<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'><id>tag:blogger.com,1999:blog-14041117</id><updated>2008-12-31T08:56:49.858-08:00</updated><title type='text'>An Obsession with Everything Else</title><subtitle type='html'></subtitle><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default?start-index=26&amp;max-results=25'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.obsessionwithfood.com/everything_else'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>321</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-14041117.post-5127436877564921971</id><published>2008-12-31T07:35:00.001-08:00</published><updated>2008-12-31T08:56:49.882-08:00</updated><title type='text'>Unit Testing The UI</title><content type='html'>The conventional wisdom on unit tests is that they stop at the UI. There are, after all, QA-centric tools that automate the tasks of pressing buttons, choosing menu items, and putting text into forms.&lt;br /&gt;&lt;br /&gt;But a lot of what I do at work is writing code that generates web pages. It's been relatively easy to introduce unit tests into utility classes or the service layer, but I wanted to give myself the same stability in the UI universe. Since my team is good about separating &lt;a href="http://en.wikipedia.org/wiki/Model_view_controller"&gt;models, views, and controllers&lt;/a&gt;, I figured out an easy way to bring unit tests up against the UI: Execute methods in the controller, and then inspect the resulting model.&lt;br /&gt;&lt;br /&gt;We use the &lt;a href="http://www.springsource.org/"&gt;Spring framework&lt;/a&gt;, which makes this concept pretty easy. It even includes &lt;a href="http://en.wikipedia.org/wiki/Mock_object"&gt;mock objects&lt;/a&gt; for setting up HTTP requests and responses. The generic Spring flow enforces good separation: Your controller's handle method gets called and is expected to return a ModelAndView object. Your unit test can call the same method with appropriately configured mock request objects and then peer into the model object to make sure that values are set properly.&lt;br /&gt;&lt;br /&gt;Since our views are usually JSPs, which require a running web container, I don't think there's a good way to write unit tests for that layer. But since the view relies on the model being correct, and the unit tests can look at the model, I think it's a pretty good stopping point. I don't want to rewrite those automated button-pushing tools, after all.&lt;br /&gt;&lt;br /&gt;Trying to incorporate this idea into my Mac/iPhone programming, I realized I could go a little bit further than I could in the web container world. I'm working on an iPhone app to enable &lt;a href="http://www.obsessionwithfood.com/2008_01_01_blog-archive.html#8031156668623095778"&gt;crazy Derrick meal planning&lt;/a&gt;, and one of the controllers is responsible for showing me things I have to do on any one day. I started with that as an experiment. To enable my "UI-ish" unit tests, I have split Apple's default template for this controller into a definite Model object and a definite "ViewLogic" object. The ViewLogic object handles code for formatting the date, determining if the table is present, how many sections it has, and what the title of each section is. All of these vary depending on the state of the model, because there are different categories of things to worry about for any given day, and you could have 0-3 of those categories. My test invokes methods on my controller that update the model object. Then I inspect that model and execute various methods on the ViewLogic object to make sure they're returning correct values based on the model. &lt;br /&gt;&lt;br /&gt;Now I can refactor the methods in ViewLogic, some of which have a relatively high &lt;a href="http://en.wikipedia.org/wiki/Cyclomatic_complexity"&gt;cyclomatic complexity&lt;/a&gt;, with a high confidence that I won't break the code that relies on it or introduce new bugs.&lt;br /&gt;&lt;br /&gt;There are a couple of consequences to this, though. One is a bit of class bloat. I can re-use the model object for at least one other view, but the ViewLogic object is tailored to that one view. The other consequence is that the ViewLogic has become an adapter, converting the methods that the framework expects to find (which have context about views and such that don't exist in the unit test world) into methods I define. Rather than doing the normal thing, then:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {&lt;br /&gt;    // do calculations about number of rows&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I do this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {&lt;br /&gt;    return [self rowsInSection: section];&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Not necessarily bad, but it does make those methods a little anemic. I could pass nil as the table view, but I could see my code at some point needing to execute a method on the table view. So better to isolate "number of rows" logic in a method where I can call it even if I don't have a runtime UITableView object.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/5127436877564921971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=5127436877564921971&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5127436877564921971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5127436877564921971'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/12/unit-testing-ui.html' title='Unit Testing The UI'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-7897777614176808999</id><published>2008-12-28T14:24:00.000-08:00</published><updated>2008-12-29T11:47:22.982-08:00</updated><title type='text'>Annotation Processors</title><content type='html'>Java 1.5 introduced the concept of &lt;a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html"&gt;annotations&lt;/a&gt;, markup that you can attach to classes, fields, and methods. We didn't use them a lot at my last job, but in my last round of interviews the subject came up, and I spent some time researching them. At my new job, we &amp;mdash; or, rather, the frameworks we use &amp;mdash; use them heavily.&lt;br /&gt;&lt;br /&gt;I've come to really like them. Like any technology, they can become a hammer that makes everything look like a nail, but they offer a powerful layer on top of code. In particular, they can be used to add &lt;a href="http://en.wikipedia.org/wiki/Cross-cutting_concern"&gt;cross-cutting&lt;/a&gt; functionality that shouldn't be shoehorned into every object. To take one example, you could set up an "Audited" annotation that would fire every time a method is called and would write out the invoking information to a log file. The audited method wouldn't know anything about it and wouldn't need to support the auditing infrastructure: The framework around the method would see the annotation and do the work.&lt;br /&gt;&lt;br /&gt;Because they are treated as code, they serve as more-correct documentation. If you the programmer see an @Audited annotation, you know that the given method gets audited. Contrast that with a normal Javadoc comment, which might reflect the method's behavior when it was first written (not audited) but not the current behavior (audited).&lt;br /&gt;&lt;br /&gt;Working with annotations in running code is fairly easy. Java's reflection layer, which lets you inspect and manipulate runtime objects, has simple methods that tell you if a given structural element has a given annotation.&lt;br /&gt;&lt;br /&gt;But I was intrigued by the concept of annotation processors, compile-time parsers that work off of the annotations in the source. Sun's primary use case for an annotation processor is generating additional files based on an annotation (classes to support XML marshalling and unmarshalling of a given object, for instance). I wondered if you could write an annotation processor that would inspect the source code looking for issues. The earlier you catch problems, the cheaper they are to fix: If I could use annotations to do extra checks on certain code, I could make them compile-time issues that would thus never get released.&lt;br /&gt;&lt;br /&gt;I had a particular scenario in mind. At my work, we have some JavaScript code that invokes some of our system's Java objects through indirection, &lt;a href="http://en.wikipedia.org/wiki/Json"&gt;JSON&lt;/a&gt;, and reflection. Java supports method overloading  (two methods with the same name but different arguments) but JavaScript does not. I inadvertently discovered how this could be a problem when I added an overloaded method to a Java object used by our JavaScript, and it broke our website (in development) for a couple of hours as we tracked down the problem. The JavaScript layer was invoking the new method, not the old method. Now I know this and avoid method overloading in the relevant classes. But some new programmer some day won't know and may make the same mistake. Or I might forget. Every time you have a process that people need to remember, you guarantee that one day someone will forget.&lt;br /&gt;&lt;br /&gt;Unfortunately, documentation and examples for this system are sparse. So in the interest of helping others who have similar goals, I've attached the source code below. (There is a &lt;a href="http://en.wikipedia.org/wiki/Visitor_pattern"&gt;Visitor pattern&lt;/a&gt; implementation as well, but that is even more poorly documented. I need to revisit my code and figure that out at some point.) The code could use some touch-up, but it gets the idea across. Note that the code uses the Java 1.6 APIs, not the markedly different and unsupported Java 1.5 APIs.&lt;br /&gt;&lt;br /&gt;My custom annotation is called RequireUniqueMethodNames and is defined as a class-level annotation. If present, my annotation processor (which you fire by adding a -processor argument to javac) will inspect the class to ensure that there are no overloaded methods. It is smart enough &amp;mdash; and the annotation processing system is powerful enough &amp;mdash; to distinguish overridden methods (allowed) from overloaded methods (not allowed). &lt;br /&gt;&lt;br /&gt;I also allow you to exempt certain method names and not check private methods. These were to support the reality of the legacy code, in which there are overloaded methods that aren't exposed to JavaScript and private methods (which wouldn't be called by anyone) with duplicate names. A refactoring task in my queue will make these attributes irrelevant, but they're there for the moment.&lt;br /&gt;&lt;br /&gt;First the annotation declaration:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;@Documented&lt;br /&gt;@Target(ElementType.TYPE)&lt;br /&gt;@Retention(RetentionPolicy.CLASS) // required with @Documented&lt;br /&gt;@Inherited&lt;br /&gt;public @interface RequireUniqueMethodNames {&lt;br /&gt;    /** Whether or not to enforce duplicate names on private methods */&lt;br /&gt;    boolean enforceOnPrivates() default false;&lt;br /&gt; &lt;br /&gt;   String[] exemptedMethodNames() default {};&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And now the processor code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public class UniqueMethodNamesProcessor extends AbstractProcessor {&lt;br /&gt;&lt;br /&gt;    // stateful!&lt;br /&gt;    private boolean enforcePrivates = false;&lt;br /&gt;&lt;br /&gt;    //stateful!&lt;br /&gt;    private List&amp;lt;String&amp;gt; exemptedMethodNames;&lt;br /&gt;&lt;br /&gt;    public boolean process(Set&amp;lt;? extends TypeElement&amp;gt; annotations,&lt;br /&gt;   RoundEnvironment curEnv) {&lt;br /&gt;&lt;br /&gt; // each element in the annotations set is a single Annotation&lt;br /&gt; // (and since we only support one, it's always RequireUniqueMethodNames)&lt;br /&gt; for (TypeElement te : annotations) {&lt;br /&gt;&lt;br /&gt;     // just in case we're passed a stray&lt;br /&gt;     if (!te.getQualifiedName().toString().equals("com.ea.sp.community.annotation.RequireUniqueMethodNames")) {&lt;br /&gt;  notice("Sent a stray annotation: " + te.getQualifiedName().toString());&lt;br /&gt;  continue;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     for (Element element : curEnv.getElementsAnnotatedWith(te)) {&lt;br /&gt;  enforcePrivates = false; // reset each time&lt;br /&gt;  exemptedMethodNames = new ArrayList&amp;lt;String&amp;gt;();&lt;br /&gt;&lt;br /&gt;  // double-check to ensure that each is a class&lt;br /&gt;  if (!(element instanceof TypeElement)) {&lt;br /&gt;      error("Sent the wrong type of element: " + &lt;br /&gt;     element.getKind().name());&lt;br /&gt;      continue;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  TypeElement clazz = (TypeElement)element;&lt;br /&gt;&lt;br /&gt;  // figure out the annotation instance used on this class&lt;br /&gt;  // note that class might have many annotations, but we know ours is&lt;br /&gt;  // one of them&lt;br /&gt;  for(AnnotationMirror am : elemUtils().getAllAnnotationMirrors(clazz)){&lt;br /&gt;   // use elemutils... because we declare this annotation as&lt;br /&gt;   // inherited, so we want to make sure we catch the subclasses&lt;br /&gt;   if (am.getAnnotationType().asElement().getSimpleName()&lt;br /&gt;       .toString().equals("RequireUniqueMethodNames")) {&lt;br /&gt;&lt;br /&gt;       // todo-dfs: is there a better way to construct the&lt;br /&gt;       // ExecutableElement to use for direct lookup?&lt;br /&gt;   Map&amp;lt;? extends ExecutableElement,? extends AnnotationValue&amp;gt; &lt;br /&gt;       methodMap = elemUtils().getElementValuesWithDefaults(am);&lt;br /&gt;   for (ExecutableElement curMethod : methodMap.keySet()) {&lt;br /&gt;       AnnotationValue value = methodMap.get(curMethod);&lt;br /&gt;       if (curMethod.getSimpleName().toString().&lt;br /&gt;    equals("enforceOnPrivates")) {&lt;br /&gt;    enforcePrivates = &lt;br /&gt;        ((Boolean)value.getValue()).booleanValue();&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       if (curMethod.getSimpleName().toString().equals("exemptedMethodNames")) {&lt;br /&gt;    &lt;br /&gt;    List&amp;lt;? extends AnnotationValue&amp;gt; exemptValues =&lt;br /&gt;        (List&amp;lt;? extends AnnotationValue&amp;gt;)value.getValue();&lt;br /&gt;    for (AnnotationValue arrayValue : exemptValues) {&lt;br /&gt;        exemptedMethodNames.add(arrayValue.toString());&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;  List&amp;lt;TypeElement&amp;gt; classChain = calculateClassHierarchy(clazz);&lt;br /&gt;&lt;br /&gt;  // this will be a list of method objects that we maintain for&lt;br /&gt;  // each class in the list. because we want to check uniqueness&lt;br /&gt;  // within the universe of one class, not across the entire&lt;br /&gt;  // source base&lt;br /&gt;  List&amp;lt;ExecutableElement&amp;gt; methods = &lt;br /&gt;      calculateAllSuperMethods(classChain.subList(0,(classChain.size() - 1)));&lt;br /&gt;&lt;br /&gt;  screenDuplicateMethods(methods,clazz);&lt;br /&gt;&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt; return false; &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /** Collate all the methods for every incoming TypeElement. This does not do&lt;br /&gt;     *  a duplicate check because non-annotated classes are allowed to have&lt;br /&gt;     *  duplicate names.&lt;br /&gt;     */&lt;br /&gt;    private List&amp;lt;ExecutableElement&amp;gt;&lt;br /&gt; calculateAllSuperMethods(List&amp;lt;TypeElement&amp;gt; superclasses) {&lt;br /&gt;&lt;br /&gt; List&amp;lt;ExecutableElement&amp;gt; retVal = new ArrayList&amp;lt;ExecutableElement&amp;gt;();&lt;br /&gt; for (TypeElement curClass : superclasses) {&lt;br /&gt;&lt;br /&gt;     for (Element classMember : curClass.getEnclosedElements()) {&lt;br /&gt;  if (!classMember.getKind().equals(ElementKind.METHOD)) {&lt;br /&gt;      continue;&lt;br /&gt;  }&lt;br /&gt;  retVal.add((ExecutableElement)classMember);&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt; return retVal;&lt;br /&gt;    }&lt;br /&gt;     &lt;br /&gt; &lt;br /&gt;    /** Looks for duplicate methods in the aggregate list of method names that&lt;br /&gt;     *  have been accrued as we go up the superclass chain. &lt;br /&gt;     *  @param methodNames the list of ExecutableElements from all the superclasses&lt;br /&gt;     *  @param curClass the current TypeElement&lt;br /&gt;     */&lt;br /&gt;    private void screenDuplicateMethods(List&amp;lt;ExecutableElement&amp;gt; methods,&lt;br /&gt;                                        TypeElement curClass) {&lt;br /&gt;              &lt;br /&gt; for (Element classMember : curClass.getEnclosedElements()) {&lt;br /&gt;     &lt;br /&gt;     &lt;br /&gt;     if (!classMember.getKind().equals(ElementKind.METHOD)) {&lt;br /&gt;  continue;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     ExecutableElement method = (ExecutableElement)classMember;&lt;br /&gt;     if (method.getModifiers().contains(Modifier.PRIVATE) &amp;amp;&amp;amp;&lt;br /&gt;  !this.enforcePrivates) {&lt;br /&gt;  continue;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     // oddly, the method names are recorded as "method name" (with the quotes&lt;br /&gt;     // from getannotationvalue. so look for that&lt;br /&gt;     if (this.exemptedMethodNames.&lt;br /&gt;  contains("\"" +method.getSimpleName().toString()+ "\"")) {&lt;br /&gt;  continue;&lt;br /&gt;     }&lt;br /&gt;                &lt;br /&gt;     if (!methodNameExists(method,methods)) {&lt;br /&gt;  methods.add(method);&lt;br /&gt;  continue;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;     //method exists, but is it an override? overrides are okay&lt;br /&gt;     if (methodIsOverride(method,methods)) {&lt;br /&gt;                   continue;&lt;br /&gt;     }&lt;br /&gt;               &lt;br /&gt;     error(method.getSimpleName().toString() + " is a duplicate (not an override) of a method elsewhere in class " + method.getEnclosingElement().getSimpleName().toString() + " or in a superclass.");&lt;br /&gt; }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /** Recursively constructs a List of TypeElements that form the superclass&lt;br /&gt;     *  chain for the given class.&lt;br /&gt;     *  @param classDec the starting class to declare&lt;br /&gt;     *  @return List of superclasses, with Object at 0 and the passed-in class&lt;br /&gt;     *          at the end.&lt;br /&gt;     */&lt;br /&gt;     private List&amp;lt;TypeElement&amp;gt; calculateClassHierarchy(TypeElement startClass) {&lt;br /&gt;  TypeElement superclass = &lt;br /&gt;      (TypeElement)typeUtils().asElement(startClass.getSuperclass());&lt;br /&gt;  if (superclass == null ||&lt;br /&gt;      superclass.equals(typeUtils().getNoType(TypeKind.NONE)) ) {&lt;br /&gt;      // we're at Object, so make a list and unrecurse&lt;br /&gt;      List&amp;lt;TypeElement&amp;gt; retVal = new ArrayList&amp;lt;TypeElement&amp;gt;();&lt;br /&gt;      retVal.add(startClass);&lt;br /&gt;             return retVal;&lt;br /&gt;  } else {&lt;br /&gt;      List&amp;lt;TypeElement&amp;gt; retVal = calculateClassHierarchy(superclass);&lt;br /&gt;      retVal.add(startClass);&lt;br /&gt;      return retVal;&lt;br /&gt;  }&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     /** Does the given ExecutableElement have the same name as something in list&lt;br /&gt;      */&lt;br /&gt;    private boolean methodNameExists(ExecutableElement method,&lt;br /&gt;                                               List&amp;lt;ExecutableElement&amp;gt; methods) {&lt;br /&gt; for (ExecutableElement existingMethod : methods) {&lt;br /&gt;     if (method.getSimpleName().equals(existingMethod.getSimpleName())) {&lt;br /&gt;  return true;&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt; return false;&lt;br /&gt;    }    &lt;br /&gt;      &lt;br /&gt;    /** Determines if the passed in method is an override of a method in the list&lt;br /&gt;     *  Overrides are generally okay even if they have the same name.&lt;br /&gt;     *&lt;br /&gt;     */&lt;br /&gt;    private boolean methodIsOverride(ExecutableElement method,&lt;br /&gt;                                      List&amp;lt;ExecutableElement&amp;gt; methods) {&lt;br /&gt;       for (ExecutableElement superMethod : methods) {&lt;br /&gt;    if (elemUtils().overrides(method,superMethod,&lt;br /&gt;       (TypeElement)method.getEnclosingElement()) ) {&lt;br /&gt;               return true;&lt;br /&gt;    }&lt;br /&gt;       }&lt;br /&gt;       &lt;br /&gt;       return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;     private void notice(String message) {&lt;br /&gt;  print(Diagnostic.Kind.NOTE,message);&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     private void error(String message) {&lt;br /&gt;  print(Diagnostic.Kind.ERROR,message);&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     private void print(Diagnostic.Kind kind,String message) {&lt;br /&gt;  processingEnv.getMessager().printMessage(kind,message);&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    private Types typeUtils() {&lt;br /&gt; return processingEnv.getTypeUtils();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private Elements elemUtils() {&lt;br /&gt; return processingEnv.getElementUtils();&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/7897777614176808999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=7897777614176808999&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/7897777614176808999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/7897777614176808999'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/12/annotation-processors.html' title='Annotation Processors'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-6194076776115844707</id><published>2008-12-28T09:05:00.000-08:00</published><updated>2008-12-28T09:37:24.859-08:00</updated><title type='text'>More Unit Testing Revelations</title><content type='html'>Since &lt;a href="http://www.obsessionwithfood.com/everything_else/2008/09/testing-testing.html"&gt;I started making unit tests an active part of my development tasks at home and at work&lt;/a&gt;, I've noticed a benefit beyond the promise of stability and support for refactoring.&lt;br /&gt;&lt;br /&gt;Unit testing has made me a better programmer. More precisely, it has made me practice what I preach about good programming practice.&lt;br /&gt;&lt;br /&gt;One well-known best practice in the OO world &amp;mdash; and probably in the procedural programming world, too &amp;mdash; is to keep chunks of code small, focused, and free of side effects. This is partly for code maintenance purposes: A large method is cumbersome for a new programmer to understand. But this kind of modularity also allows other programmers (or yourself) to more easily subclass a class and add new behavior to suit new needs.&lt;br /&gt;&lt;br /&gt;But in the heat of coding a new feature, I don't always take the time to think about the best way to break it apart. I figure I'll fix it in a refactoring pass. Fifteen years as a professional programmer, and you'd think I know that that chance will never come. Bugs need fixing, or the powers-that-be move me on to a new feature.&lt;br /&gt;&lt;br /&gt;With unit tests as part of my task, I'm forced to think about testable chunks, and those are usually exactly the level of size and functionality that makes sense from an object-oriented perspective. I find myself thinking about the tests I will write as I code the class, and that in turn forces me to realize that a given method is too big, has too many side effects, or is otherwise not testable. This is especially true because I have made the decision that our team shouldn't write unit tests for retrieving objects from a database, since that is all handled by the well-vetted &lt;a href="http://www.hibernate.org"&gt;Hibernate&lt;/a&gt; library. When I write a method that retrieves objects from the database and does calculations with them, the "you'll have to test this" part of my mind realizes that I need to move the calculations into their own method (or methods) so that I can test them even without a database connection. In other words, I move specific functionality into its own method, exactly where it belongs.&lt;br /&gt;&lt;br /&gt;Over and over again, every time I code with unit tests in mind, I see myself doing more on-the-fly refactoring as I write a feature. I don't wait for the magical refactoring time that never comes: I do it as I go.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/6194076776115844707/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=6194076776115844707&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/6194076776115844707'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/6194076776115844707'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/12/more-unit-testing-revelations.html' title='More Unit Testing Revelations'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-7383740940465756708</id><published>2008-09-27T09:57:00.000-07:00</published><updated>2008-09-28T09:33:28.109-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><title type='text'>Testing Testing</title><content type='html'>Since I claim to advocate &lt;a href="http://en.wikipedia.org/wiki/Agile_software_development"&gt;Agile&lt;/a&gt; development practices, I recently decided to give &lt;a href="http://en.wikipedia.org/wiki/Unit_test"&gt;unit testing&lt;/a&gt;, the writing of simple test suites that exercise an application's basic functionality, a solid try. &lt;br /&gt;&lt;br /&gt;I've been in various organizations that have tried to do it, but the effort always founders and fails. Everyone knows unit tests are a good idea; no one ever gets any in place.  When the subject comes up at job interviews, other companies seem to have had the same problem. New features always creep ahead in priority, and once the code becomes too big, writing unit tests becomes too daunting a task. Plus, the new feature is working, more or less, so the motivation decreases to write tests for it.&lt;br /&gt;&lt;br /&gt;As I started my Mac/iPhone programming, however, I realized I had the perfect environment for unit testing: no deadline and no legacy code. So I wrote new code and the test cases to exercise it at the same time. (I didn't do test-driven development, where you write the tests first and then write the code to make them work.)&lt;br /&gt;&lt;br /&gt;I haven't since become a unit testing zealot &amp;mdash; at least I don't think so &amp;mdash; but I have a renewed enthusiasm. In a normal development scenario, I would have written all my support classes and then a UI so that I could test them. If there was a bug, I'd have to step through all the layers I had written to find it. With unit testing, I found several bugs long before I had a UI. When I did attach a UI, the fact that it worked smoothly out of the gate was anticlimactic: I knew the underlying structure was solid, after all.&lt;br /&gt;&lt;br /&gt;Recently, I thought of some code reorganization I could do on the project as well as some significant performance improvements. I spent an hour or so doing the work, shuffling this here and that there. I set up my unit tests to run, and they passed: I could assert that my changes hadn't broken anything major. That's a really good feeling; there was a bit of a rush when I saw "Build Succeeded" flicker by in the log.&lt;br /&gt;&lt;br /&gt;Even with my short experiment, I realized the two major benefits of unit testing: catching bugs earlier ("The earlier you catch bugs, the cheaper they are to fix," runs one programmer mantra) and making sure that major code changes don't affect the core functionality.&lt;br /&gt;&lt;br /&gt;But how to bring that to work? At Maxis, we have a lot of pressure to deliver features, an always-busy team, and a big ball of legacy code even for the website. Just like every other tech company. One of my co-workers and I have talked about it, and the only answer seems to be: just start writing them, and keep them running. The first few will be tough, because you need a support structure for them (especially in the form of mock objects, layers that conform to the contract of the real layer, but deliver static, simplistic data), but once they get going, hopefully it becomes easier to add new ones. I have a squirrely bit of logic in one piece of code: That's my first candidate.&lt;br /&gt;&lt;br /&gt;The problem is selling the concept to the business side. I think in a lot of companies, the extra time spent writing unit tests is seen as time that could be spent writing a new feature. In a way that's true, but I now see that unit tests actually are a feature: stability. No one wants an unstable system, but the investment in stability doesn't obviously pay off for a long time. In fact, if you've done it right, the investment never seems to pay off because the system doesn't have as many problems. You can't measure how many problems there would have been if the investment hadn't been made. But you can measure the number of users using a new feature. So you get instant feedback that it was a good addition, and you provide a metric. How does unit testing compete against that?</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/7383740940465756708/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=7383740940465756708&amp;isPopup=true' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/7383740940465756708'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/7383740940465756708'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/09/testing-testing.html' title='Testing Testing'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-4394667818769180081</id><published>2008-09-26T07:53:00.000-07:00</published><updated>2008-09-26T15:31:47.758-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><title type='text'>A Server Programmer On The Client</title><content type='html'>&lt;p&gt;I have been teaching myself Objective-C over the last few months. I'm starting with some Mac programs, but let's be honest: The end goal is iPhone applications. I have some already in mind, and I'm actively working on one right now.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I have been a server-side programmer for the last 10 years. And certainly my current senior-programmerness has been forged in the universe of databases, web servers, and application servers. Not even browsers, though I know enough JavaScript and HTML to get by.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;A couple months into my client side hobby, I realize how much my experience influences my thinking. A part of me keeps worrying about scalability and threading issues as I write my first application, and I have to remind myself to not overdesign. These are big issues on web servers: You are almost guaranteed to have multiple threads running through every piece of code in your site, especially on my current project, &lt;a href="http://www.spore.com"&gt;spore.com&lt;/a&gt;, where we average 40,000 people logged in at any given moment using 200 active threads on each of our 20 application servers. HTTP requests, complete with database queries and code-level processing, need to move through the system as fast as possible, and you need to be aware of the pieces that modify state outside of that request so that you can prevent multiple threads from changing the same piece of data at the same time. But for my client app, only one person runs it at a time. And Mac OS X keeps threading issues to a minimum. (Though they can exist.)&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;On the other hand, there are facets of client-side programming that I've all but forgotten. Memory management? Who still worries about that? It's possible to have memory leaks in Java, but the native garbage collection takes care of all but the most esoteric memory management tasks. (Of course, it can also introduce its own performance problem, as anyone can attest who's watched their web app stop dead for a fraction of a second every few seconds as the garbage collector kicks in.) Sure, Objective-C 2.0 has garbage collection, but that limits your application to Mac OS X 10.5. The primary book I'm using, &lt;a href="http://www.powells.com/biblio/2-9780321503619-0"&gt;&lt;em&gt;Cocoa Programming For Mac OS X&lt;/em&gt;&lt;/a&gt;, still follows the reference counting pattern, a fragile system if ever there was one. I haven't yet looked at the autorelease pool pattern that other tutorials use. (On the other hand, being able to discard the memory for objects whenever you want is a nice side effect. You never know when Java's garbage collection scythe will come a-calling, rendering the language's destructors useless for cleanup.)&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;And linking befuddles me after 13 years with Java (3 on the client side). In Java, you just put the class file where the virtual machine can find it, and it's available to your code. Application servers introduce a bit more complexity, but not much. For my application, in order to integrate regular expressions (an aside: Really? They're not built in?), I have to pass an argument to the package linker and deal with cryptic error messages when it doesn't work.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;But overall I'm enjoying this diversion into fat client programming. I think in the end it's making me a more well-rounded developer. As someone whose resume loudly proclaims "All-Purpose Programmer," this is probably a good thing.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/4394667818769180081/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=4394667818769180081&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/4394667818769180081'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/4394667818769180081'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/09/server-programmer-on-client.html' title='A Server Programmer On The Client'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-3453372947224336831</id><published>2008-07-05T09:01:00.000-07:00</published><updated>2008-07-06T08:53:11.877-07:00</updated><title type='text'>Puzzle Quest</title><content type='html'>&lt;p&gt;I shouldn&amp;rsquo;t like &lt;a href="http://en.wikipedia.org/wiki/Puzzle_quest"&gt;Puzzle Quest: Challenge of the Warlords&lt;/a&gt;.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;For one thing, it&amp;rsquo;s a role-playing game, and I tend not to like computerized versions of the genre. I find them boring: full of tedious dialog, boring storylines, random battles, and the inevitable &amp;ldquo;leveling up&amp;rdquo; times that require you to fight a lot of small battles just to get enough hit points to take on the next &lt;a href="http://en.wikipedia.org/wiki/Big_bad"&gt;Big Bad&lt;/a&gt;.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;For another, Puzzle Quest&amp;rsquo;s mechanic is novel for RPGs but trite in the larger universe: You fight each battle by playing &lt;a href="http://en.wikipedia.org/wiki/Bejeweled"&gt;Bejeweled&lt;/a&gt;, the ubiquitous puzzle game from &lt;a href="http://www.popcap.com"&gt;PopCap&lt;/a&gt;, against your opponent. It&amp;rsquo;s &lt;a href="http://www.penny-arcade.com/comic/2007/3/28/an-excerpt-from-the-book-of-deeds/"&gt;a ridiculous conceit if you think about it&lt;/a&gt;, though no more so than other video game mechanics, I guess. As you clear colored gems off the board, you build up enough &lt;em&gt;mana&lt;/em&gt;, or ingredients, to cast offensive and defensive spells. Clear a set of skulls, and you do direct damage to your opponent. Clear gold coins and you earn money. Clear a set of four and you get an extra turn. Rinse and repeat &lt;em&gt;ad infinitum&lt;/em&gt;.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;So why am I so addicted to an RPG that uses one of the most overexposed casual games as its sole means of battle? An equally addicted friend and I have chatted about this, and we can&amp;rsquo;t figure it out. I&amp;rsquo;ve done every little side quest. I&amp;rsquo;ve collected every possible companion. I&amp;rsquo;ve trained up my mount. I&amp;rsquo;ve forged items from runes I have found throughout the kingdom. I have, in short, opted to do all the things that I dislike about RPGs. Melissa has even quipped that she should buy me a &lt;a href="http://en.wikipedia.org/wiki/Dice#Non-cubical_dice"&gt;d20&lt;/a&gt;. (As an aside, this game cost me $15 on XBox Live, and I&amp;rsquo;ve poured hours into it, giving it the best gameplay-to-price ratio in my collection.)&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I think one key factor is that it transforms the solo game of Bejeweled into a two-person strategy game. You can&amp;rsquo;t clear any row you want: You have to consider how your opponent will be able to use the board. And the different strengths and weaknesses of the various monsters create different strategies. I favored green and red jewels in any fight against trolls, because the combination would let me drain the troll&amp;rsquo;s blue mana, which he can and does use constantly to regenerate health. I favored green gems in battles against elves just to prevent them from getting them, since they need very few to cast some nasty spells.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;There are other facets of the game the make it appealing &amp;mdash; the scant dialog you find is often humorous, and the music is at least good enough to not get tiring &amp;mdash; but the heart of the game is the &amp;ldquo;strategic Bejeweled&amp;rdquo; component.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;There was one thing about the game I didn&amp;rsquo;t like. By the time I got to the final boss, I had hit the top level for my character, I had forged some impressive items, and I had done virtually every side quest. But with him I was massively outclassed. Within minutes, he had obliterated me, and I had barely made a dent in his health. I expected an epic match, but this felt way out of balance with respect to the rest of the game. It felt disheartening, not challenging. On the other hand, I did beat him after a dozen or so attempts, so maybe it wasn&amp;rsquo;t as unbalanced as I thought. But I wonder if the casual player who doesn&amp;rsquo;t go to the lengths I did will find the final boss truly impossible. Perhaps the game adjusts his levels based on the player.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Maybe there&amp;rsquo;s a little RPG fan in me after all, just waiting for a game like Puzzle Quest to draw it out. I doubt I&amp;rsquo;ll be digging into Final Fantasy any time soon, but I am now one of the countless hordes waiting for Puzzle Quest&amp;rsquo;s forthcoming Plague Lord expansion.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/3453372947224336831/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=3453372947224336831&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/3453372947224336831'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/3453372947224336831'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/07/puzzle-quest.html' title='Puzzle Quest'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-133813746876475138</id><published>2008-04-10T15:10:00.001-07:00</published><updated>2008-04-10T16:27:00.280-07:00</updated><title type='text'>Writing Pause</title><content type='html'>Once I start at Maxis, I'm going to cut back on my freelance writing. One reason is that the team will be moving toward crunch mode. But the main reason would have been true in any new job: I want to build my team's trust in me before I do writer things such as going to wine tastings in the middle of the day or visiting wineries or whatever. My current employer has given me a lot of freedom in this regard, but it didn't at first.&lt;br /&gt;&lt;br /&gt;My clients have been pretty understanding. One asked me to do a piece because it wouldn't require any fieldwork (plus, they said, my copy is pretty clean, and they were a bit desperate for it). (I turned them down.) I bowed out of a Chronicle lede because I won't have time to give it the research it deserves, but then my editor asked if I'd be willing to write a 400-word review/commentary of a new book. &lt;br /&gt;&lt;br /&gt;Overall, my life as a published writer is taking a breath. But I'm not letting it sit idle. Instead, I have writing skills that I want to develop. I'll be working on essays &amp;mdash; a genre that I adore above all other non-fiction but which I find difficult to write, despite the real argument you could make that each OWF post is an essay exercise. I'm also planning to work on my narrative nonfiction abilities; I want to be the type of writer who casts features in the form of conflicts, crises, and resolutions. I want my pieces to have a story flow that brings the reader into the prose. And I want to continue to grow my newfound library research knowledge. If I may use a metaphor from my &lt;a href="http://en.wikipedia.org/wiki/Spore_%28video_game%29"&gt;newest development project&lt;/a&gt;, those skills are currently in the Tidepool phase and I want them to be at the Civilization phase. My existing research skills have earned compliments from &lt;a href="http://www.artofeating.com"&gt;Ed Behr&lt;/a&gt;, which counts for a lot, but I want to go further.&lt;br /&gt;&lt;br /&gt;But aside from my writing skills, I intend to devote some time to my programming projects. My recent job quest and my parallel investigation of the latest, greatest technologies have reminded me how much I like programming. I go through little phases where I believe I no longer do, but it doesn't usually take much to remind me of the sheer joy of coding. I'm working on a "wine notebook/cellar management" system, which I may or may not make more available. The application allows me to have a project where I have to explore technologies in depth but not spend months and months and months working with them. I can do a Spring/Hibernate/YUI version, as I am now, or a Mac OS X/iPhone version, or a Ruby on Rails version, all using the same database. Doing any one of those gives me enough of a grounding in the technologies to work on more complicated projects (at least in theory &amp;mdash; I already know a fair amount about Spring, something about Hibernate, but nothing at all about YUI).</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/133813746876475138/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=133813746876475138&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/133813746876475138'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/133813746876475138'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/04/writing-pause.html' title='Writing Pause'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-6553437066910444087</id><published>2008-04-07T09:59:00.000-07:00</published><updated>2008-04-08T14:11:34.667-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Coding'/><title type='text'>Dashcode/Spore Countdown Widget</title><content type='html'>&lt;p&gt;For &lt;a href="http://www.obsessionwithfood.com/2008_04_01_blog-archive.html#8103504410516408635"&gt;obvious reasons&lt;/a&gt;, I&amp;rsquo;ve been reading all the press about &lt;a href="http://www.spore.com"&gt;Spore&lt;/a&gt;. At one point, I decided to do the cheesy, geeky thing and find a Dashboard widget that counts down to Spore&amp;rsquo;s release. Only I couldn&amp;rsquo;t find one that I liked. So I did the even geekier thing and wrote my own.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Melissa made noises about Kool-Aid and cults, but if you&amp;rsquo;ve ever played with &lt;a href="http://developer.apple.com/tools/dashcode/"&gt;DashCode&lt;/a&gt;, you&amp;rsquo;ll realize that &lt;a href="http://www.obsessionwithfood.com/SporeCountdown.zip"&gt;my widget&lt;/a&gt; is a barely-modified version of the default countdown template. I fiddled with some colors, set some attributes, and added a graphic from the &lt;a href="http://www.spore.com/fansitekit.php"&gt;fansite kit&lt;/a&gt;. Other than downloading the fansite kit, which took eons on our slow connection, the whole widget probably took 30 minutes of my time.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I&amp;rsquo;ve played with DashCode before, but always with ambitious projects in mind that demanded more than the Dashboard infrastructure would allow. I was impressed with the application at the time, but ultimately my widget ideas didn&amp;rsquo;t go anywhere. Having a project go from start to finish in a short time made me appreciate just how easy DashCode is. I didn&amp;rsquo;t need to do any coding for the widget; any moderately technical person could have set it up.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/6553437066910444087/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=6553437066910444087&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/6553437066910444087'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/6553437066910444087'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/04/dashcodespore-countdown-widget.html' title='Dashcode/Spore Countdown Widget'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-7788194604568180938</id><published>2008-03-21T10:28:00.000-07:00</published><updated>2008-04-08T13:06:19.230-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Coding'/><title type='text'>AppleScript: Open A Bunch Of Links In Safari</title><content type='html'>I recently judged the American Wine Blog Awards. My list of nominees came as a spreadsheet with the URLs of the blogs and their names, grouped by category. Because there were 30 or more nominations in some categories, I didn&amp;rsquo;t want to open each link individually in Safari. I turned to my old friend and nemesis: AppleScript.&lt;br /&gt;&lt;br /&gt;It took a bit of online sleuthing to figure out the quirks in Safari&amp;rsquo;s dictionary, but I eventually got a script that opens a set of URLs, each in its own tab, in a new window. I&amp;rsquo;ve included it below for anyone who might find it useful. It works well enough, given that I only needed to run it 8 times. It&amp;rsquo;s pretty brain dead as it is: There is no error checking and you have to have the URLs in a return-delimited list, which is what you get when you copy URLs out of a column in Excel. If you run it, you&amp;rsquo;ll also notice that it opens an extra tab at the &amp;ldquo;beginning&amp;rdquo; of the new window. If it bothered me, I would figure out how to remove the tab, but for 8 runs I could just click the close box. I only tested it on Mac OS X 10.5 and Safari 3.0.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;set clipText to the clipboard&lt;br /&gt;&lt;br /&gt;set AppleScript's text item delimiters to {"&lt;br /&gt;"}&lt;br /&gt;set urlList to text items of clipText&lt;br /&gt;set AppleScript's text item delimiters to {""}&lt;br /&gt;tell application "Safari"&lt;br /&gt; set newDoc to make new document&lt;br /&gt; set currWindow to front window&lt;br /&gt; repeat with currentURL in urlList&lt;br /&gt;  make new tab at the end of tabs in currWindow with properties {URL:currentURL}&lt;br /&gt; end repeat&lt;br /&gt;end tell&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/7788194604568180938/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=7788194604568180938&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/7788194604568180938'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/7788194604568180938'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/03/applescript-open-bunch-of-links-in.html' title='AppleScript: Open A Bunch Of Links In Safari'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-5425814188835223184</id><published>2008-03-03T13:02:00.000-08:00</published><updated>2008-03-03T16:16:04.778-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Writing I Like'/><title type='text'>Writing I Like: Nicholson Baker Writes About Wikipedia</title><content type='html'>I think I&amp;rsquo;ve liked every piece of Nicholson Baker nonfiction that I&amp;rsquo;ve ever read, but I thought I would call out &lt;a href="http://www.nybooks.com/articles/21131"&gt;his piece about Wikipedia&lt;/a&gt; for my new-and-exciting &lt;em&gt;Writing I Like&lt;/em&gt; category. Ostensibly, the essay is a review of &lt;em&gt;Wikipedia: The Missing Manual&lt;/em&gt;, but in true NYT Review of Books fashion, that&amp;rsquo;s a lightweight skeleton supporting the piece&amp;rsquo;s muscle.&lt;br /&gt;&lt;br /&gt;Baker has whole battlefronts of conflict at his disposal to spice up his piece: He paints the modern-day Wikipedia as an mostly-unseen war between the keepers of the encyclopedic truth and its would-be spammers and trolls. Even within the legitimate ranks, he finds tension: There are aggressive purgers debating against article inclusionists. (And, really, is it any surprise that the author of &lt;a href="http://www.codysbooks.com/product/info.jsp?isbn=9780375726217"&gt;&lt;em&gt;Double Fold&lt;/em&gt;&lt;/a&gt; sides with the &amp;ldquo;let&amp;rsquo;s include everything&amp;rdquo; camp?) There is even his conflict between his life as a newly enthusiastic Wikipedia editor and his life as a father and husband with household obligations.&lt;br /&gt;&lt;br /&gt;But this piece really shines with its use of specifics. Baker has a finely tuned eye for detail backed by an obsessive knowledge-seeking mind. Consider his accounts of Wikipedia vandalism:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Some articles are vandalized a lot. On January 11, 2008, the entire fascinating entry on the aardvark was replaced with "one ugly animal"; in February the aardvark was briefly described as a "medium-sized inflatable banana." &lt;/blockquote&gt;&lt;br /&gt;He doesn&amp;rsquo;t bother cracking jokes. Who needs to with source material such as this? &lt;br /&gt;&lt;br /&gt;As with &lt;a href="http://www.obsessionwithfood.com/everything_else/2008/03/writing-i-like-wireds-psychologist-and.html"&gt;the Wired piece about the Netflix Prize&lt;/a&gt;, Baker&amp;rsquo;s piece shines because of his presence in the piece (a more overt presence than that of the writer of the Netflix piece). He talks about how he got drawn in to Wikipedia editing, the battles he won (and lost) to keep articles in the system, the addictive pull of debates with other editors and conflicts with vandals. &lt;br /&gt;&lt;br /&gt;I finished the piece feeling a little lighter, a little happier, and a little more inclined to edit Wikipedia articles.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/5425814188835223184/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=5425814188835223184&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5425814188835223184'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5425814188835223184'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/03/writing-i-like-nicholson-baker-writes.html' title='Writing I Like: Nicholson Baker Writes About Wikipedia'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-1154348752823688312</id><published>2008-03-02T20:55:00.000-08:00</published><updated>2008-03-03T10:11:29.328-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Writing I Like'/><title type='text'>Writing I LIke: Wired's Psychologist And The Netflix Prize</title><content type='html'>&lt;p&gt;I spend a lot of time here griping about laughable published writing that shouldn&amp;rsquo;t have slipped past an editor&amp;rsquo;s red pen. Let&amp;rsquo;s look a piece that makes me smile in a good way: Wired&amp;rsquo;s &lt;a href="http://www.wired.com/techbiz/media/magazine/16-03/mf_netflix?currentPage=1"&gt;piece about an English psychologist/operations engineer&lt;/a&gt; who has rocketed up the leaderboard for the Netflix prize, a $1 million reward for anyone who can improve the company&amp;rsquo;s recommendation system by 10 percent.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;First of all, hats off to the author, Jordan Ellenberg, for distilling complex math into a usable form for the smart, but not expert, reader. Ellenberg is a mathematician in his own right, and he summarizes the high-math concepts used by the competitors into common English, using analogies to illustrate his points. As someone who increasingly finds himself writing technical, &amp;ldquo;wine geek&amp;rdquo; wine pieces for a mainstream, layperson audience, I am impressed by his skill.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;But the biggest draw of this piece is the compelling narrative: A classic &amp;ldquo;little guy beats the big guy&amp;rdquo; scenario. Good fiction, which is the model for narrative non-fiction, revolves around conflict, and the author has rightfully used the inherent battle &amp;mdash; a single psychologist and his high-school daughter, the math consultant, trouncing teams of math and computer science professionals working with sophisticated programs &amp;mdash; as the axis of his piece. From conflict comes crisis, the boiling point, and Ellenberg provides it with the current status: all the contestants close to the final prize from a numerical point of view, but very far from a realistic point of view. Ideally, one wants a resolution as well, but that remains in the future, an acceptable ending for a newsy narrative nonfiction piece.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The author doesn&amp;rsquo;t lock himself into this story, though. &amp;ldquo;Digress often, but never for long,&amp;rdquo; reads one of the few axioms laid out in the classic &lt;a href="http://www.codysbooks.com/product/info.jsp?isbn=9780452261587"&gt;&lt;em&gt;The Art and Craft of Feature Writing&lt;/em&gt;&lt;/a&gt;. Ellenberg spins a quick history of Netflix, a brief description of the prize, a look into the minds of the Netflix statisticians, the surprising collaboration of the competing groups, and more, all through short digressions that linger just long enough: As soon as you start to think, &amp;ldquo;Get back to the psychologist!&amp;rdquo; he does.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;My final point &amp;mdash; though there are other things to like in this piece &amp;mdash; is that Ellenberg allows himself to be in the piece. Feature wells don&amp;rsquo;t often permit first-person narrative for obvious reasons: Too much first-person, and the reader begins to wonder why s/he should care about the writer so much. Among my clients, only &lt;a href="http://www.artofeating.com"&gt;The Art of Eating&lt;/a&gt; finds it natural, though others allow it when it makes a difference. But Ellenberg sometimes steps away from his story to give his own view: &amp;ldquo;He refers to the psychological model underlying their mathematical approach as &amp;lsquo;crude.&amp;rsquo; His tone suggests that if I weren&amp;rsquo;t taping, he might use a stronger word.&amp;rdquo; Ellenberg exposes himself to the reader, but in doing so draws a more detailed picture of the person you actually care about. And he doesn&amp;rsquo;t forget to show and not tell, though he has a harder time in his straightforward reporting sections: Small details like the notebook and the elderly Dell allow the reader to paint a more vivid picture.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I might try to incorporate a bit more color and rhythm into the prose, were I writing this, but the story is good enough that only someone looking for nits will drill down on that.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/1154348752823688312/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=1154348752823688312&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/1154348752823688312'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/1154348752823688312'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/03/writing-i-like-wireds-psychologist-and.html' title='Writing I LIke: Wired&apos;s Psychologist And The Netflix Prize'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-2693062359687525811</id><published>2008-02-20T21:50:00.000-08:00</published><updated>2008-02-20T22:44:18.345-08:00</updated><title type='text'>Google Chart</title><content type='html'>I recently discovered &lt;a href="http://code.google.com/apis/chart/"&gt;Google&amp;rsquo;s Chart&lt;/a&gt; service, which allows you to query Google with a bunch of parameters and get back a dynamically generated chart. The idea intrigued me, but I couldn&amp;rsquo;t think of an immediate use for it until I remembered that I had yet to &lt;a href="http://www.obsessionwithfood.com/2008_02_01_blog-archive.html#7584949495620659100"&gt;publish the results of a survey&lt;/a&gt; I ran on OWF in September. Now was the time, I thought.&lt;br /&gt;&lt;br /&gt;After a day of playing with the system, I have to say I&amp;rsquo;m pretty impressed. You get a wide range of styles, you can set the data in a variety of ways, and you can fiddle with labels and legends. It has some quirks: I couldn&amp;rsquo;t get the horizontal axis to quite line up with my data points on my bottom graph, and you have to give the data for the bar chart as a percentage instead of a straight value. But once I got the hang of it, the graphs came together quickly and looked nice.&lt;br /&gt;&lt;br /&gt;Now I just need an excuse to put a Venn diagram on OWF.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/2693062359687525811/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=2693062359687525811&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/2693062359687525811'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/2693062359687525811'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/02/google-chart.html' title='Google Chart'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-4841911436687659142</id><published>2008-02-02T06:39:00.000-08:00</published><updated>2008-02-02T09:13:06.383-08:00</updated><title type='text'>Amusing Grammar Slip</title><content type='html'>Mediabistro, a paywalled website for writers, recently posted the transcript of a seminar on grammar. I read through it &amp;mdash; it&amp;rsquo;s all stuff I know, unfortunately &amp;mdash; and noticed this sentence in the section on pronoun agreement: &amp;ldquo;The next example is probably my favorite because I didn't make it up. I get silly text messages from Virgin Mobile for their promotional crap, and it had a lot of errors in it. &amp;rdquo;&lt;br /&gt;&lt;br /&gt;I know it&amp;rsquo;s just a transcript. It may even be what the teacher said. But that particular sentence in that particular section tickled me.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/4841911436687659142/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=4841911436687659142&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/4841911436687659142'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/4841911436687659142'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/02/amusing-grammar-slip.html' title='Amusing Grammar Slip'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-3593392789971292050</id><published>2008-01-31T14:15:00.000-08:00</published><updated>2008-01-31T14:16:50.249-08:00</updated><title type='text'>Link Blogs</title><content type='html'>One of the many blog genres is the link blog, a site that just provides links to other sites. It sounds boring, but some of the blogosphere&amp;rsquo;s most popular sites are link blogs: Jason Kottke adds little content at &lt;a href="http://kottke.org"&gt;kottke.org&lt;/a&gt;; nor do the &lt;a href="http://www.boingboing.net"&gt;boing boingers&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Without realizing what I was doing, I started a link blog in July of 2007. I put up a &amp;ldquo;snacks&amp;rdquo; feature on OWF, a light feature that would just point people to interesting items on the Web. &lt;a href="http://www.obsessionwithfood.com/everything_else/2007/07/derricks-web-snacks-process.html"&gt;I made it easy to maintain&lt;/a&gt;, and I started populating it.&lt;br /&gt;&lt;br /&gt;I&amp;rsquo;ve come to realize just how addictive such a feature is. Its main purpose is still to provide links that my readers may or may not find interesting, links that may fuel a coffee break at work. But I often find myself &amp;ldquo;snacking&amp;rdquo; a link that I want to be able to retrieve at some later point. Blogger makes this even more compelling: I host the main snack blog on blogspot, and the default Blogger header has a search box for the entire site. And once &lt;a href="http://www.forkandbottle.com"&gt;Jack&lt;/a&gt; prompted me to add descriptions to my links (which show up in the RSS feed but not yet on OWF), I realized I could add keywords that would speed up the retrieval.&lt;br /&gt;&lt;br /&gt;So have I read &lt;a href="http://www.masa.on.net/The%20Mathematics%20of%20Change%20Ringing.pdf"&gt;The Mathematics Of Change Ringing&lt;/a&gt; [pdf] yet? Well, not completely. But I can revisit it at some later point without trying to remember where I saw the link. To find the link just now, I went to &lt;a href="http://derrickswebsnacks.blogspot.com"&gt;the main snack page&lt;/a&gt;, typed the word &amp;ldquo;bell&amp;rdquo; into the navbar at the top of the screen, and went right to the entry.&lt;br /&gt;&lt;br /&gt;It&amp;rsquo;s been liberating in an odd way. I can tuck links into this little corner of the Internet and recover them at will. It&amp;rsquo;s become a &lt;a href="http://en.wikipedia.org/wiki/Pensieve#Pensieve"&gt;Pensieve&lt;/a&gt; of sorts. I can snack a link, close the window, and revisit it when I have time.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/3593392789971292050/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=3593392789971292050&amp;isPopup=true' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/3593392789971292050'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/3593392789971292050'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/01/link-blogs.html' title='Link Blogs'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-416036581639885277</id><published>2008-01-26T22:36:00.000-08:00</published><updated>2008-01-26T22:50:24.962-08:00</updated><title type='text'>The Atlantic's Variety Cryptic</title><content type='html'>The blogosphere has been abuzz with &lt;a href="http://www.theatlantic.com"&gt;The Atlantic&amp;rsquo;s&lt;/a&gt; recent decision to tear down its long-standing paywall.&lt;br /&gt;&lt;br /&gt;While most have mentioned trawling the archives for this or that article, few have made the obvious observation: &lt;a href="http://www.theatlantic.com/doc/200801u/puzzler"&gt;The variety cryptic&lt;/a&gt;, by Cox and Rathvon, is part of the package. Perhaps that&amp;rsquo;s because the &lt;a href="http://www.boingboing.net"&gt;boing boingers&lt;/a&gt; and &lt;a href="http://www.kottke.org"&gt;Jason Kottke&lt;/a&gt; don&amp;rsquo;t actually do variety cryptics, but I feel like it hasn&amp;rsquo;t even shown up on my puzzle lists. Maybe it&amp;rsquo;s always been online, and I&amp;rsquo;ve never looked?&lt;br /&gt;&lt;br /&gt;Cryptic crosswords are crossword puzzles in which the clues have the word definition, but they also have a hefty amount of wordplay to get you to the answer. Thus each clue in a cryptic is really two clues. It&amp;rsquo;s up to you to determine where one ends and the other begins, and deciphering the clue can be tricky: I often figure out what word should go in the space provided and then try to reverse-engineer the wordplay.&lt;br /&gt;&lt;br /&gt;A variety cryptic adds yet another dimension. In a variety cryptic, the answer to the cryptic clues don&amp;rsquo;t necessarily fit into the grid the way you&amp;rsquo;d expect. You might have to add a letter, drop a letter, change one fragment to another, or make words &amp;ldquo;warp&amp;rdquo; from one part of the grid to another. I remember one variety cryptic, published in &lt;em&gt;The Enigma&lt;/em&gt;, of course, where words that contained the names of European currencies had to be transformed so that those letters became EURO. &lt;em&gt;Remarkable&lt;/em&gt; became &lt;em&gt;reeuroable&lt;/em&gt;, for instance.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/416036581639885277/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=416036581639885277&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/416036581639885277'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/416036581639885277'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/01/atlantics-variety-cryptic.html' title='The Atlantic&apos;s Variety Cryptic'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-4439880928508599930</id><published>2008-01-16T10:11:00.000-08:00</published><updated>2008-01-16T10:14:15.963-08:00</updated><title type='text'>Technology Rules</title><content type='html'>&lt;a href="http://www.wisn.com/education/15056479/detail.html"&gt;Some high school students discovered an asteroid.&lt;/a&gt; Neat.&lt;br /&gt;&lt;br /&gt;But the best part is the summary of connections mentioned on Slashdot. The Wisconsin students, &amp;ldquo;used a telescope in New Mexico, belonging to a college in Michigan, that they controlled over the Net.&amp;rdquo; Gotta love the Internets.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/4439880928508599930/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=4439880928508599930&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/4439880928508599930'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/4439880928508599930'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/01/technology-rules.html' title='Technology Rules'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-6374793228741757879</id><published>2008-01-15T13:46:00.000-08:00</published><updated>2008-01-15T13:51:46.209-08:00</updated><title type='text'>Quote Of The Day: Starting Fires With Fish</title><content type='html'>From Joystiq, describing the latest news of Uwe Boll, the German director most famous for his big-screen videogame adaptations:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;&amp;hellip;the infamous German director and pouting pugilist will see his future endeavors financially constrained after his latest $70 million video game adaptation, "In the Name of the King: A Dungeon Siege Tale," set the box-office alight with all the effectiveness of two moist fish furiously rubbed together.&lt;/blockquote&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/6374793228741757879/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=6374793228741757879&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/6374793228741757879'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/6374793228741757879'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/01/quote-of-day-starting-fires-with-fish.html' title='Quote Of The Day: Starting Fires With Fish'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-5358171090361759057</id><published>2008-01-14T10:59:00.000-08:00</published><updated>2008-01-14T11:12:25.260-08:00</updated><title type='text'>Quote Of The Day: This Is Why People Don't Like Goblins</title><content type='html'>Jerry, from &lt;a href="http://www.penny-arcade.com"&gt;Penny Arcade&lt;/a&gt;, writes of his re-entrance into World of Warcraft.&lt;br /&gt;&lt;blockquote&gt;After seven blissful levels of wondering why I ever quit WoW, I ended up on some featureless beach in Feathermoon waiting for some stupid, almost nonsensical drop. After that, I talked to a Goblin whose most pressing concern (in a world whose very crust was cracked by perpetual War) was his tremendous thirst. Would I go and get something for him to drink? I've got water, but he doesn't want it. He only wants to drink the glands of some twelve-foot tall plant man. It's like, listen. This is why people don't like Goblins.&lt;/blockquote&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/5358171090361759057/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=5358171090361759057&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5358171090361759057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5358171090361759057'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/01/quote-of-day-this-is-why-people-dont.html' title='Quote Of The Day: This Is Why People Don&apos;t Like Goblins'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-1793223155225307055</id><published>2008-01-11T00:04:00.000-08:00</published><updated>2008-04-08T13:06:42.880-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Coding'/><title type='text'>Randomness</title><content type='html'>When Pim and I looked at &lt;a href="http://www.chezpim.com/blogs/2008/01/menu-for-hope-1.html"&gt;the prizes for Menu For Hope&lt;/a&gt;, we were surprised to see that a number of people had won more than one prize. I know, theoretically, that an even distribution among prize winners is one possible outcome while lists with duplicates are much more likely. But it's one thing to know it, and another thing to accept it. An iPod's shuffle feature is truly random, but it often seems as if the shuffle is favoring one artist or album. (That's why iTunes now offers a "smart" shuffle that diminishes randomness in favor of distributing artists/albums.) And in the TV show Numb3rs, an evenly distributed set of points is a clue that someone has tried to make something look random. True random numbers aren't spread out equally.&lt;br /&gt;&lt;br /&gt;Just to convince myself, I wrote some code to demonstrate this unintuitive aspect of randomness.&lt;br /&gt;&lt;br /&gt;First, draw 1 number from a set of 10. The probability for each number is 1/10th. Do this 10,000 times to get a bunch of results to chart, and you end up with an almost even distribution across those 10 numbers, demonstrating the fairness of the random draw:&lt;br /&gt;1 1024&lt;br /&gt;2 1042&lt;br /&gt;3 992&lt;br /&gt;4 952&lt;br /&gt;5 974&lt;br /&gt;6 1034&lt;br /&gt;7 995&lt;br /&gt;8 993&lt;br /&gt;9 1001&lt;br /&gt;10 993&lt;br /&gt;&lt;br /&gt;In that run, each "turn" was a single draw. Now make each turn 10 draws. In Menu For Hope, many contributors entered multiple raffles, and drawing more than once from the same pool simulates this. We expect each number to come up once, because that's what probability tells us. But it doesn't work that way, at least not necessarily. On one run, I got this set of results:&lt;br /&gt;2&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;2&lt;br /&gt;10&lt;br /&gt;2&lt;br /&gt;6&lt;br /&gt;8&lt;br /&gt;3&lt;br /&gt;&lt;br /&gt;Even though the probability of each number is 1/10, you end up with single numbers repeated. There are three 2s and 2 6s.&lt;br /&gt;&lt;br /&gt;When I ran the 10-draw turn 10,000 times, I got these results:&lt;br /&gt;&lt;br /&gt;1 unique numbers 0&lt;br /&gt;2 unique numbers 0&lt;br /&gt;3 unique numbers 6&lt;br /&gt;4 unique numbers 187&lt;br /&gt;5 unique numbers 1274&lt;br /&gt;6 unique numbers 3457&lt;br /&gt;7 unique numbers 3528&lt;br /&gt;8 unique numbers 1373&lt;br /&gt;9 unique numbers 169&lt;br /&gt;10 unique numbers 6&lt;br /&gt;&lt;br /&gt;Here I counted the unique numbers in each set. 10 unique numbers means a perfect distribution. 1 unique number means that every random draw hit the same number. I didn't implement the logic to separate duplicates (the 6 in the previous example) from the triples (the 2 in the prior example). Or quadruples, for that matter. This was a simple simulation.&lt;br /&gt;&lt;br /&gt;The odds of randomly getting an even distribution are pretty low. You are roughly 600 times more likely to have 6 or 7 unique numbers, meaning that some of those numbers are duplicated (or tripled or whatever). You&amp;rsquo;re 200 times more likely to have a scenario where half the numbers are missing. You&amp;rsquo;re 30 times more likely to have just 4 unique numbers: a whole host of repeats. The numbers looked similar on multiple runs.&lt;br /&gt;&lt;br /&gt;This makes sense if you think about it. In the second draw of the 10-draw turn, you have a 10 percent chance of drawing a duplicate. If you don&amp;rsquo;t, you have two unique numbers, and on the third draw you have a 20 percent chance of duplicating one of the existing values. By the time you get to the tenth draw, there&amp;rsquo;s a 90 percent chance that you&amp;rsquo;ll draw a number that&amp;rsquo;s already been drawn, assuming all the others were unique.&lt;br /&gt;&lt;br /&gt;Menu For Hope, of course, is much more complicated. Raffle ticket purchasers can stack the odds in their favor by buying more tickets for a given raffle. Prizes with fewer bidders have different odds than those with more bidders. And it was about 100 draws across 9,000 raffle tickets. But even the simple version in this post, where each of just 10 numbers has an equal probability, shows that duplicates and triplicates should be commonplace. So it's not surprising that some people won multiple prizes: It would have been more surprising if none had.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/1793223155225307055/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=1793223155225307055&amp;isPopup=true' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/1793223155225307055'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/1793223155225307055'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/01/randomness.html' title='Randomness'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-2186942661906529918</id><published>2008-01-10T19:48:00.000-08:00</published><updated>2008-03-04T10:45:05.881-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Paging An Editor'/><title type='text'>Which Paragraph Looks Better?</title><content type='html'>In my &lt;a href="http://www.sfgate.com/cgi-bin/article.cgi?f=/c/a/2008/01/10/WI2UU28UA.DTL&amp;feed=rss.wine"&gt;article about Franconian beer&lt;/a&gt;, I have these paragraphs:&lt;br /&gt;&lt;blockquote&gt;But even the best Franconian brewers aim for the everyday drinker, not the thrill-seeking beer snob. Waltman contrasts Belgium's prestigious brews with Franconia's farmhouse ales.&lt;br /&gt;&lt;br /&gt;Belgian beers kind of hit you over the head," he says. "They have unusual flavors; they're big. In Belgium, if you ask for recommendations for 10 breweries, they would all be cafes: People sitting around sipping these strong beers. In Germany, and especially in Franconia, beer is what you drink all day long. The beers are designed to be drunk." Simple flavors, well-balanced hops and low alcohol create a drink that can go with your weeknight dinner or your afternoon break.&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;This is the text as I submitted it and as the Chronicle ran it. But when I had a chance to review the piece before publication, I looked at it again and posed a question to my editor. Should it read, "Waltman contrasts Franconia's farmhouse ales with Belgium's prestigious brews"?&lt;br /&gt;&lt;br /&gt;One of my writing mantras &amp;mdash; one of the ones in &lt;a href="http://www.obsessionwithfood.com/everything_else/2007/07/writing-mantras-screensaver.html"&gt;my screen saver&lt;/a&gt; &amp;mdash; is &amp;ldquo;Keep related concepts together.&amp;rdquo; Following that rule, the paragraph should read the way I asked. The first &amp;ldquo;Belgium&amp;rdquo; segues into a digression about that country&amp;rsquo;s beers. But then your internal reader sees &amp;ldquo;Franconia Franconia Belgium Belgium.&amp;ldquo; Because those are proper nouns, it feels, for lack of a better word, loud.&lt;br /&gt;&lt;br /&gt;What do you think?</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/2186942661906529918/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=2186942661906529918&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/2186942661906529918'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/2186942661906529918'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2008/01/which-paragraph-looks-better.html' title='Which Paragraph Looks Better?'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-6848645811775982573</id><published>2007-12-26T09:35:00.000-08:00</published><updated>2008-03-04T10:45:05.881-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Paging An Editor'/><title type='text'>Paging An Editor: An Article About Champagne Vinegar?</title><content type='html'>&lt;p&gt;I think the point of &lt;a href="http://www.nytimes.com/2007/12/26/dining/26feed.html?_r=1&amp;ex=1356411600&amp;en=4c0ed8c3ee0bf1d2&amp;ei=5088&amp;partner=rssnyt&amp;emc=rss&amp;oref=slogin"&gt;this New York Times article&lt;/a&gt; is that Champagne vinegar is good. But that point only shows up in the last two paragraphs: The rest is a stretched analogy about the flip side of Champagne's connotations of luxury. So what is the point of this article, anyway?</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/6848645811775982573/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=6848645811775982573&amp;isPopup=true' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/6848645811775982573'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/6848645811775982573'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2007/12/paging-editor-article-about-champagne.html' title='Paging An Editor: An Article About Champagne Vinegar?'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-5517189250154832747</id><published>2007-12-17T21:28:00.000-08:00</published><updated>2007-12-17T21:34:30.649-08:00</updated><title type='text'>No. Really. No Ads.</title><content type='html'>I got an email today asking something about placing content on OWF. I gave my standard response &amp;mdash; I don't take ads on OWF. The author wrote back. Clearly I misread her intent: I thought she wanted to take out ads.&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;The text links I referred to in my previous email will not consume any advertisement space (header/footer/side bar) on your web pages and are to be placed within the existing content of your web pages.&lt;br /&gt;&lt;br /&gt;Each of these paragraphs will be custom-written to go nicely with the rest of your webpage and will add unique content to your webpage. These paragraphs will contain natural text links only and will not affect your page ranking with Google.&lt;br /&gt;&lt;br /&gt;I will write keeping the content, quality and audience of your website in view. After reviewing your website thoroughly, I'll write the paragraphs' content accordingly.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Would an ad by any other name still smell &amp;hellip; ? If I say no ads, I mean it. It doesn't matter how they look.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/5517189250154832747/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=5517189250154832747&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5517189250154832747'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5517189250154832747'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2007/12/no-really-no-ads.html' title='No. Really. No Ads.'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-3953939320840236564</id><published>2007-12-17T18:39:00.000-08:00</published><updated>2007-12-17T18:55:15.136-08:00</updated><title type='text'>Heroes Fans Fan Service?</title><content type='html'>On my way home, I listened to the most recent episode of &lt;a href="www.thetenthwonder.com"&gt;The 10th Wonder&lt;/a&gt;, a podcast about Heroes. The hosts announced that they'd be taking a break for the next few weeks, in contrast to last season's hiatus, where they kept the podcast going as "a service to the fans."&lt;br /&gt;&lt;br /&gt;My inner schoolboy ("inner?" you're thinking) emerged, and I giggled. Three guys giving &lt;a href="http://en.wikipedia.org/wiki/Fan_service"&gt;fan service&lt;/a&gt;? I'll pass, &lt;a href="http://www.obsessionwithfood.com/everything_else/2007/04/are-you-sure-youre-not-gay.html"&gt;no matter what my friends might think of me&lt;/a&gt;. Call me when &lt;a href="http://www.frenchmaidtv.com/"&gt;French Maid TV&lt;/a&gt; does it.&lt;br /&gt;&lt;br /&gt;Oh, right. I guess FMTV is almost all fan service.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/3953939320840236564/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=3953939320840236564&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/3953939320840236564'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/3953939320840236564'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2007/12/heroes-fans-fan-service.html' title='Heroes Fans Fan Service?'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-5334692179824168474</id><published>2007-12-17T10:37:00.000-08:00</published><updated>2007-12-17T10:39:33.220-08:00</updated><title type='text'>Gmail Ads</title><content type='html'>I was amused by the current juxtaposition of context-sensitive ads appearing alongside my gmail pane:&lt;br /&gt;&lt;br /&gt;Retaining Walls&lt;br /&gt;Genesis Stoneworks Freeee! in home estimates Call 800-287-5400&lt;br /&gt;&lt;br /&gt;Random Number Generation&lt;br /&gt;Stanford finance risk mgmt courses. Learn new ideas &amp; proven techniques&lt;br /&gt;&lt;br /&gt;Official Gaiam Yoga Store&lt;br /&gt;Official source for Gaiam yoga mats yoga kits, props &amp; accessories.</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/5334692179824168474/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=5334692179824168474&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5334692179824168474'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/5334692179824168474'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2007/12/gmail-ads.html' title='Gmail Ads'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-14041117.post-3655755031115688763</id><published>2007-12-06T12:54:00.000-08:00</published><updated>2007-12-06T12:58:00.483-08:00</updated><title type='text'>SOHAKSC</title><content type='html'>&lt;p&gt;Today's Slylock Fox, shown near the bottom of &lt;a href="http://joshreads.com/?p=1359"&gt;this Comics Curmudgeon post&lt;/a&gt;, asks readers &amp;mdash; children, in theory &amp;mdash; to rearrange the letters in six words to spell the names of items in the picture.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Go ahead, try to figure out what SOHAKSC spells. You can probably guess the word based on how English structures these things, but do you know what it means? Can you find it in the picture? How many kids do you know who could nail this one?&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/3655755031115688763/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=14041117&amp;postID=3655755031115688763&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/3655755031115688763'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/14041117/posts/default/3655755031115688763'/><link rel='alternate' type='text/html' href='http://www.obsessionwithfood.com/everything_else/2007/12/sohaksc.html' title='SOHAKSC'/><author><name>Derrick</name><uri>http://www.blogger.com/profile/05974720556627635894</uri><email>noreply@blogger.com</email></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry></feed>