My Kind of Stupid

Web development, user interfaces, and other development topics.

  1. Getting Everyone to Adopt Your Solution

    I see people approaching problems like this a lot: someone sees a better solution and thinks that everyone would do it if they just understood that it was better. They give a presentation, and then they get frustrated because everyone doesn’t immediately jump on the new solution.

    It’s not because it’s not better (though maybe it’s not! Other folks have different problems than you, so it really might not be that much better. But let’s assume it really is better for everyone). Is it actually solving everyone’s problems? Or is it just solving a particular team’s problems? The latter is nice, nobody would complain about that, but they can’t drop everything to accommodate it. They have real priorities and needs that they are working toward. You have to trust that each person and team knows what’s best for themselves, better than you do.

    If it’s really important that everyone buy in, you’ve got to get an advocate, figure out how the team can fit it into their schedule, and make it super easy. Work directly with each team so you understand their particular needs as they implement your solution, and so you can help them and take some of the load off them. This takes a lot more time, but will build trust and relationships that are necessary to maintain the new solution going forward.

    Published at

    Permalink
  2. Regular Team Rotation

    On large software projects, communication is key. There are so many ways we can try to facilitate regular communication between engineering teams, but I believe one of the best is regular rotation of engineers between the teams. Working on a new team fosters knowledge sharing, shared ownership and responsibility, better technical decisions, and builds communication and technical skills for each of the engineers.

    I define knowledge sharing between software teams as understanding what other teams do: their goals, process, products, and code. Understanding each of these things enables effective communication and planning on your own team. When someone new is rotated onto the team, they have the opportunity to see these processes first-hand, perform them, and improve them with an outside perspective. Performing something yourself is a great way to actually understand it. When that new person rotates off the team, their understanding will also become out of date, but now that can be resolved with a conversation with someone now on that team. Now they know who to talk to, and their shared base of understanding enables them to have that conversation more effectively than starting from scratch.

    A shared base of understanding also contributes to shared ownership and responsibility for the entire project. When things go wrong, we don’t always get the opportunity to learn what went wrong, how it was resolved, and how to prevent it in the future. Missing these leave us with a lack of confidence in our ability to troubleshoot and resolve future issues, and result in a culture of avoiding issues that we don’t understand. This culture spreads very quickly. Regular rotation between teams provides a lot more opportunity to observe these issues and learn from them. It builds a network of people to talk to, and that shared base of understanding that you can use to figure out an issue even without immediately recognizing it.

    Regular rotation between teams builds a network of people to talk to organically. Knowing who to talk to about a given topic is invaluable, but difficult to build from scratch. We have the opportunity to find this network every day that we work closely with several people. Increasing that number by rotating between teams helps that network grow so you can be more confident in finding the answer to your problem. Working with new people regularly helps you learn to communicate with different people who have difference communication styles. Likewise, working with new codebases helps you learn different patterns and how to fit your own style into a new team.

    These are all the reasons I think regular team rotation is very important. It’s difficult to implement for several reasons, like reluctance from individuals, aligning individuals with managers, and balancing staffing needs with department goals. So I don’t like making hard rules about rotation, but instead keeping it as a goal and metric to be tracked and discussed with each individual and team regularly.

    I’ve tried hard to keep pair programming out of this discussion so far, but bear with me now. I truly believe team rotation is essential to a healthy team, while pair programming is a little more optional. But pairing will multiply these results and make them much more visible and permanent. Pairing often involves rotation within a team that achieves these same results, so rotation between teams is a natural extension.

    Published at

    Permalink
  3. Launchd Gem Servers on OS X

    Usually when I need documentation for a RubyGem I'm working with, I go to RubyDoc.info. Today, however, it was not responding. After catching up on Twitter it occurred to me that I could read the documentation locally since I already had the gem installed.

    Running gem rdoc prawn1 generates documentation for the prawn gem. Then gem server will start a server on localhost:8808 hosting the generated RDoc.

    This worked fine, but it occurred to me that starting and stopping the gem server whenever I want to see the documentation is annoying. OS X has the concept of launch agents which can be kept alive by the operating system whenever you are logged in. I thought I would make a launch agent to run a gem server so that I could open it whenever I wanted.

    Unfortunately, running gem server in a default shell uses the version of Ruby that ships with OS X. I have gems installed in different versions of Ruby using chruby, and I might want to see any of them depending on what project I'm working on. So I decided to run a server for each version of Ruby I have installed (except the system Ruby). Each server runs on a different port chosen based on the Ruby version it supports. Ruby 2.0.0 is available at localhost:8200, 2.1.4 is available at localhost:8214, and 2.2.1 is at localhost:8221.

    I wrote a script and an accompanying launch agent plist to run it and posted them in a gist.


    1. There was one catch to generating the documentation. I keep my ~/.gemrc with gem: --no-ri --no-rdoc so that bundle installs go quickly, but in this case it was causing the gem rdoc command to fail. I had to remove that option to successfully generate the documentation.

    Published at

    Permalink
  4. Rendering array properties in Ember.js

    While working on photo uploads in my latest project, I needed a network activity indicator to reflect background network usage. That way, users could navigate away from the upload form and see that uploads were still going in the background. Since I'm using Ember.js I created a component to display the indicator. I created an array in my ApplicationController to track each upload. For each element in the array, I rendered a network activity indicator.

    The concept was pretty straightforward and in a simple case worked fine. But when I removed an indicator after an upload was done, I got a strange error:

    TypeError: Cannot read property 'destroy' of undefined
        at Ember.CollectionView.Ember.ContainerView.extend.arrayWillChange (http://localhost:8888/assets/ember.js?body=true:23233:16)
        at null._contentWillChange (http://localhost:8888/assets/ember.js?body=true:23140:10)
        at sendEvent (http://localhost:8888/assets/ember.js?body=true:2302:14)
        at notifyBeforeObservers (http://localhost:8888/assets/ember.js?body=true:2671:5)
        at Object.propertyWillChange (http://localhost:8888/assets/ember.js?body=true:2497:3)
        at set (http://localhost:8888/assets/ember.js?body=true:2763:15)
        at Object.Ember.trySet (http://localhost:8888/assets/ember.js?body=true:2832:10)
        at Object.Binding._sync (http://localhost:8888/assets/ember.js?body=true:6765:15)
        at Object.DeferredActionQueues.flush (http://localhost:8888/assets/ember.js?body=true:5565:24)
        at Object.Backburner.end (http://localhost:8888/assets/ember.js?body=true:5656:27)

    The stacktrace indicated the problem is when I set the new array of uploads. From the description of Ember.CollectionView it looked like the problem is with updating the rendered template from the array:

    Ember.CollectionView is an Ember.View descendent responsible for managing a collection (an array or array-like object) by maintaining a child view object and associated DOM representation for each item in the array and ensuring that child views and their associated rendered HTML are updated when items in the array are added, removed, or replaced.

    Stack Overflow had an answer that explained that the problem was with updating the property bindings:

    push doesn't tell the binding to update, but pushObject does

    Since I was using a native JavaScript array, I was using native methods to add and remove uploads. I needed to use an Ember.NativeArray instead so that I could use it in my template. This is possible using Ember.A to wrap an array-like object:

    this.set('uploadActivityIndicators', Ember.A([id]));
    this.get('uploadActivityIndicators').removeObject(id);

    From what I've read, this is typical of Ember—use Ember's replacements for native JavaScript concepts to avoid any surprises like this. This is one reason why some people prefer Angular or React. I don't think this is enough of a reason to write off a framework, it's just a guideline of using Ember. It makes it more complicated to learn but doesn't make it automatically bad. Still, I need to learn more about Angular and React so I can better compare the options.

    Published at

    Permalink
  5. NSTokenField failed to query display string for a represented object

    I created an NSTokenField and everything worked great with strings as tokens. When I created a representative class to use instead, I ended up with an error message and a blank token field.

    <NSTokenFieldCell: 0x6080003c97e0>: Failed to query display string for a represented object <HMCPhotoTag: 0x608000422460>. Ignoring...

    Turns out, I just set the objectValue before I set the delegate. Setting the delegate first meant the token field could transform my objects correctly.

    Published at

    Permalink
  6. JRuby NameError: missing class or uppercase package name

    Originally posted as a gist in response to this twitter conversation.

    I'm working on a Rails application that uses the Exchange Webservices API. Unfortunately the EWS API has a weird package, but I thought that wouldn't be hard to overcome. So I wrote this script:

    require 'java'
    require 'vendor/lib/EWSJavaAPI_1.2.jar'
    
    def microsoft
      Java::Microsoft
    end
    
    version = microsoft.exchange.webservices.data.ExchangeVersion::Exchange2010_SP1
    service = microsoft.exchange.webservices.data.ExchangeService.new(version)

    Getting the ExchangeVersion enum worked fine, but trying to access the ExchangeService class resulted in this error:

    NameError: missing class or uppercase package name (`microsoft.exchange.webservices.data.ExchangeService')
    get_proxy_or_package_under_package at org/jruby/javasupport/JavaUtilities.java:54
    method_missing at file:/Users/adam/.rvm/rubies/jruby-1.7.3/lib/jruby.jar!/jruby/java/java_package_module_template.rb:14
    (root) at lib/sandbox.rb:11

    I thought the problem was with the package name, but the error is actually that the EWS API is missing its dependencies. It needed commons-httpclient and commons-logging-api on the classpath. I discovered this via clojure's error message for similar code:

    (import '(microsoft.exchange.webservices.data ExchangeService))
    ClassNotFoundException org.apache.commons.httpclient.HttpConnectionManager  java.net.URLClassLoader$1.run (URLClassLoader.java:366)

    So the real issue here is JRuby's error message. Interestingly enough, JRuby did give me a better error once commons-httpclient was on the classpath:

    MultiThreadedHttpConnectionManager.java:70:in `<clinit>': java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory

    That told me commons-logging-api was also missing. JRuby should have told me that in the first place.

    Published at

    Permalink
  7. Code Reviews

    Imagine if we didn’t judge the author of the code by how few “WTFs” it generated, but instead by how fertile it was for discussion? Or, imagine if every programmer was being judged by what they learned from the discussion. How would that change things? better still, imagine if the “manager's” job was to facilitate the learning and get the discussion back on track when it strayed into fixing-the-code territory instead of to judge people by how much they "contribute." That would be a breath of fresh air to most teams.

    An excellent point. Code reviews are valuable for getting eyes on the code, but I agree with Raganwald here that the more valuable outcome is that everyone shares in the learning. I've thought of that as learning about the work that others are doing, but Raganwald makes a good point that you learn from the discussion about the work that others are doing.

    Published at

    Permalink
  8. Kent Beck on Pair Programming

    pair programming works best with a large uncertain search space of problems and solutions. the closer to a solved problem, the less it helps

    Kent Beck (@KentBeck)

    Published at

    Permalink
  9. Implementing a Mutable Subclass in Objective-C

    I'm writing a class in Git Push to hold and fetch git blobs from Github.

    @interface GHBlob : NSObject <NSCopying, NSMutableCopying>
    @property (nonatomic, copy, readonly) NSData *content;
    ...
    @end
    
    @implementation GHBlob
    @synthesize content = _content;
    ...
    @end

    To support creating and updating blobs, I wrote a mutable subclass initialized in #mutableCopy from NSMutableCopying. This entailed redefining properties as readwrite:

    @interface GHMutableBlob : GHBlob
    @property (nonatomic, copy, readwrite) NSData *content;
    ...
    @end
    
    @implementation GHMutableBlob
    @synthesize content = _content;
    ...
    @end

    Problems

    Resynthesizing

    As shown above, I also re-synthesized the methods in the subclass. However, this resulted in the subclass's initializer breaking.

    GHBlob *newBlob = [blob mutableCopy];
    STAssertEquals(newBlob.content, blob.content, nil);
      //=> '<00000000>' should be equal to '<10cbb806>'

    On review, I noticed something I'd missed before in Apple's documentation on redeclaring properties. The redeclared property isn't synthesized in their example, it's declared @dynamic and the setter is defined by hand. I scoffed at the inconvenience (isn't this what synthesizing is supposed to avoid?), but did it anyway.

    Unreachable instance variables

    Next, the compiler complained that the _content instance variable was unreachable. Oh, so this is why we define ivars in the interface - it makes them available to subclasses.

    Summary

    So to implement NSMutableCopying, I had to explicitly declare my instance variables in the interface and explicitly define setter methods in my mutable subclass. I thought the property/synthesize syntax was saving me the trouble, but apparently it's not all rainbows and unicorns.

    @interface GHBlob : NSObject <NSCopying, NSMutableCopying> {
      NSData *_content;
    }
    @property (nonatomic, copy, readonly) NSData *content;
    ...
    @end
    
    @implementation GHBlob
    @synthesize content = _content;
    @end

    And in the mutable subclass:

    @interface GHMutableBlob : GHBlob
    @property (nonatomic, copy, readwrite) NSData *content;
    ...
    @end
    
    @implementation GHMutableBlob
    @dynamic content;
    - (void)setContent:(NSData *)content {
      _content = [content copy];
    }

    Published at

    Permalink
  10. Nil Weak Reference to an OCMock Mock Object

    While I was working on unit tests for Git Push, I wanted to mock my delegate so I could create an expectation. So I used OCMock:

    id delegate = [OCMockObject mockForProtocol:@protocol(MyDelegateProtocol)];
    [[delegate expect] someMethod];
    myClassIvar.connectionDelegate = delegate;
    [myClassIvar someOtherMethod];
    STAssertNoThrow([delegate verify], @"should have called someMethod on delegate");

    But found the assertion failing. I stepped into #someOtherMethod and discovered the delegate was nil. So I added

    STAssertNotNil(myClassIvar.connectionDelegate, @"should have set delegate");

    And watched that fail as well. Since Git Push is using ARC, I read up on retain semantics to see if I was missing something about local references. But local references are strong, so the delegate should not have been deallocated yet.

    I posted about this issue on Stack Overflow 1, where Evan suggested that OCMock may be the issue. Sure enough, creating an explicit class conforming to my delegate protocol fixed the issue.

    @interface MockDelegate : NSObject <MyDelegateProtocol>
    @property (nonatomic, strong) NSNumber *called;
    @end
    @implementation MockDelegate
    @synthesize called = _called;
    - (void)someMethod {
      _called = [NSNumber numberWithBool:YES];
    }
    @end
    
    @implementation MyClassTests
    - (void)testSomeOtherMethod {
      MockDelegate *delegate = [[MockDelegate alloc] init];
      myClassIvar.connectionDelegate = delegate;
      [myClassIvar someOtherMethod];
      STAssertTrue([delegate.called boolValue], @"should have called someMethod on delegate");
    }
    @end

    I still don't understand why it was being deallocated, but it was definitely related to OCMock. I've isolated the issue in a Github repo to make it obvious and repeatable.

    UPDATE: Erik Doernenberg of OCMock looked into the issue and answered the Stack Overflow question as well, and determined it's actually an issue with NSProxy objects and ARC in the iOS runtime. It works fine on OS X.


    1. Note that my code is slightly different, as I was using a static helper function to create my mock. I confirmed that was not the issue.

    Published at

    Permalink
  11. fillion.rb

    Yesterday Nathan Fillion tweeted about finding a permutation of his name on a website. At first I just chuckled, but it soon occurred to me that this would be a fun problem to solve with scripting.

    So I whipped up fillion.rb to peruse a webpage for permutations of a given word. It was fun to figure out a quick algorithm to find permutations, and then fight scope creep (I ended up writing a crawler for an entire domain).

    The beauty of development is being able to look for a challenge anywhere.

    Published at

    Permalink
  12. URL-Driven Development is README-Driven Development

    While working on Web Queue today I went to document the URLs to help me figure out the interface. As I was about to start writing out the URLs, I realized I could make it a lot easier on myself by giving the document some structure. So I decided to roll it into the README, which also needed to be written.

    As I wrote up the README, everything became a bit clearer. Up to that point, I had been fussing over details, learning how to use OAuth and the Netflix API while poorly maintaining a few different classes. Writing the README allowed me to step back and conceive the system as a whole, which made the necessary code much more clear to me. That's README-driven development.

    My projects often go like this. I'm almost always learning a new technology or library, so I can't always start off with a good architecture. But starting with the interface can make things a lot clearer.

    Writing out the URLs for a project is pretty similar to writing the README. It has the same goal, figuring out the interface of a project, but focused on web applications instead of any other project. And as I'm learning from personal experience, that can be exactly what's needed for a good project.

    Published at

    Permalink
  13. New Site

    I am very excited by the Internet and how easy it is today to get a project started.

    Case in point, my new site.

    I'm also hosting my blog there now.

    Published at

    Permalink
  14. TIL, Grails edition

    I’ve unfortunately been experiencing the magic of Grails lately.

    TIL:

    new JSONObject([key: []]) == new JSONObject([key: []])

    and

    new JSONArray([]) == new JSONArray([])

    but

    new JSONObject([key: new JSONArray([])]) != new JSONObject([key: new JSONArray([])])

    This is so much fun.

    Update: This is due to a non-implementation of JSONArray.hashCode(). JIRA logged here: http://jira.grails.org/browse/GRAILS-7770

    Published at

    Permalink
  15. Write a README

    Reading code documentation to understand a system is like learning how to drive by inspecting the washer fluid dispenser, then a spark plug, then the brake pads. Write a README.

    Published at

    Permalink
  16. 90% of your users are "idiots"

    And, in good conscience, don’t you want to be an idiot when you’re on the other side of the screen? I do! I want to be an idiot! Please let me be an idiot. I want things to “just work”.

    Completely agree. It's easy to lose sight of this.

    Published at

    Permalink
  17. Silence Test::Unit tests

    I've written several tests that have the unfortunate side effect of writing to stdout or stderr, polluting my pretty stream of dots.

    Thanks to a post on Benevolent Code, I decided to silence them (gist).

    setup :silence_output
    
    # Redirects stderr and stdout to /dev/null.
    def silence_output
      @orig_stderr = $stderr
      @orig_stdout = $stdout
    
      # redirect stderr and stdout to /dev/null
      $stderr = File.new('/dev/null', 'w')
      $stdout = File.new('/dev/null', 'w')
    end
    
    teardown :enable_output
    
    # Replace stdout and stderr so anything else is output correctly.
    def enable_output
      $stderr = @orig_stderr
      $stdout = @orig_stdout
      @orig_stderr = nil
      @orig_stdout = nil
    end

    Update:

    The same thing in RSpec (gist):

    RSpec.configure do |config|
      config.before(:all, &:silence_output)
      config.after(:all, &:enable_output)
    end
    
    # Redirects stderr and stdout to /dev/null.
    def silence_output
      @orig_stderr = $stderr
      @orig_stdout = $stdout
    
      # redirect stderr and stdout to /dev/null
      $stderr = File.new('/dev/null', 'w')
      $stdout = File.new('/dev/null', 'w')
    end
    
    # Replace stdout and stderr so anything else is output correctly.
    def enable_output
      $stderr = @orig_stderr
      $stdout = @orig_stdout
      @orig_stderr = nil
      @orig_stdout = nil
    end

    Published at

    Permalink
  18. Quoted-printable in Ruby

    I'm working on reading emails sent from ActionMailer, and was noticing the weird form of what I thought was URL-encoding in an HTML section of a multipart email, e.g.

    <html lang=3D"en">

    I also noticed the line length and endings:

    blah blah … blah bl=
    ah blah blah

    I shrugged these off as quirks of ActionMailer or SMTP and moved on to removing/decoding them.

    Two hours later, when googling for info about them because they didn't seem consistent, I stumbled across Quoted-printable encoding. I recognized it from the headers in the email, and wondered if someone had written a gem or something to decode it. Then I find this - String#unpack was able to do it for me all along.

    It's great that Ruby (and sometimes Rails) can do things like this out of the box. I just wish I was smart enough to look.

    Published at

    Permalink
  19. Test-Driven Drudgery

    I'll admit it. I hate test-driven development while I'm actually doing it. It's boring and tedious. I get lost in details while trying to get some test to work. Maybe I just need to do more katas, but it gets old fast.

    Katas make it look so nice and easy. You just think of the right test, write it, and make it pass. But in practice I have a much bigger picture in mind that I want to implement, and I just can't limit myself to building it one atom at a time, or I'll lose it. Sometimes, I just want to hack.

    So I can sympathize when I see people writing a bunch of code to get some massive feature ready, and then filling in tests at the end. But it's wrong. It seems harmless ("I'm still testing, right?") because the difference can be subtle. Writing tests after the fact means you're writing your tests to fit your code, instead of the other way around. You're trying to achieve that code coverage metric instead of thinking hard about each feature, branch, and interface.

    Just like writing code without TDD, it can often work just fine. But it's not scalable and it results in poorly-tested and/or poorly-written code.

    I'm not just preaching, I live this dilemma every day. TDD sucks, but man, does it ever improve the quality of my code.

    Sometimes I just hack. But most of the time, I drop in TODO's where I need to remember a big picture and get to testing first.

    Published at

    Permalink
  20. About.me

    I made an about.me page. At the very least, it will be a central repository for where to find me. Hopefully I can get a better background eventually.

    Published at

    Permalink
  21. ActiveRecord Hangs On to Destroyed Relations

    Rails' ActiveRecord has a nifty #destroy method that almost everyone already knows about. The documentation for #destroy1 helpfully states

    Deletes the record in the database and freezes this instance to reflect that no changes should be made (since they can’t be persisted).

    What is less clear is how it works when you call it on a relationship. To be sure, it destroyed the database record. What it also unfortunately does it hangs on to the reference in the parent object.

    Let's throw down with some sample code.

    class Thing < ActiveRecord::Base
      has_one :child
    end
    
    class Child < ActiveRecord::Base
      belongs_to :thing
    end
    
    zengarden> thing = Thing.new
    => #<Thing id: nil, name: nil, created_at: nil, updated_at: nil>
    zengarden> thing.save
    => true
    zengarden> thing.create_child :name => "Child"
    => #<Child id: 4, name: "Child", thing_id: 1,...>
    zengarden> thing.child
    => #<Child id: 4, name: "Child", thing_id: 1...>
    zengarden> thing.child.destroy
    => #<Child id: 4, name: "Child", thing_id: 1...>
    zengarden> thing.child
    => #<Child id: 4, name: "Child", thing_id: 1...>

    Arg. I just destroyed it! I don't want it hanging around in my cache. If I really wanted to save it, I should have saved it before or during the destroy call. I can of course invalidate my cache by calling

    thing.child(true)

    which correctly returns nil. But if I don't know to do that immediately after the destroy, I get errors like

    TypeError: can't modify frozen hash

    because code used later naïvely calls

    if thing.child
      thing.child.attributes = new_attributes
    end

    expecting that if a child exists, it should be updated.

    For the sake of argument, let's say that maybe the behavior isn't so bad, maybe this is what people expect or could use. In that case, what a terrible error message. I'm not familiar enough with the Associations internals to suggest where a better one could be injected, but something along the lines of

    Can't modify a destroyed record

    would be miles better.

    Published at

    Permalink
  22. Read Link Later Works With New Twitter

    Version 1.1.0 of Read Link Later now works with New Twitter. Rejoice!

    Sorry for the delay. New Twitter came to me at a bad time, where I didn't have the time to devote to updating Read Link Later. Fortunately, we're in business now!

    The only issue is that the New Twitter uses their own API through JavaScript, so I don't have a way of knowing when a tweet is loaded. To work around this, I look for new tweets to add the Read Later links to every 100 ms. If I could hack in a custom event, that would be something.

    Published at

    Permalink
  23. Complex Associations with factory_girl

    factory_girl is great for DRYing up test code and making tests isolated and maintainable. What it's not so great at is any association more complicated than has_one/belongs_to. I found a nice trick on Stack Overflow for has_many associations.

    The Factory.after_ hooks appear to be the only way to do this successfully.

    It's a shame, because it makes it really painful to use factories. What used to be a one-line call to create whatever you need becomes writing your own factories that call factory_girl factories.

    Published at

    Permalink
  24. Read Link Later

    I'm excited to publish my first Google Chrome extension, Read Link Later. From the detail page:

    Read Link Later adds an Instapaper "Read Later" link to each tweet in a Twitter.com feed. The link retrieves each external URL in the tweet and adds it to your Instapaper queue for reading later.

    Unfortunately I can't add these links to clients, that would be even more helpful.

    Published at

    Permalink
  25. Happiness

    iwasamonkey:

    Happiness

    Happiness

    Published at

    Permalink
  26. GoboLinux

    onethingwell:

    GoboLinux is a modular Linux distribution: it organizes the programs in your system in a new, logical way. Instead of having parts of a program thrown at /usr/bin, other parts at /etc and yet more parts thrown at /usr/share/something/or/another, each program gets its own directory tree, keeping them all neatly separated and allowing you to see everything that’s installed in the system and which files belong to which programs in a simple and obvious way.

    Off-topic, I know, but this strikes me as an eminently sensible approach.

    Sounds like what OS X does with applications. Very sensible.

    Published at

    Permalink
  27. Grid Layout As Bad As Table Layout?

    The more I think about this, the more this statement bothers me. @tavon said that grid layouts like 960 grid are as bad as table layouts. His reasoning was that the classes are purely presentational; it's presentation affecting markup the same way tables used to.

    But that's just misuse of the layouts. You shouldn't pull something massive and generic like a grid layout and not modify it to suit your website. Wouldn't you modify your reset stylesheet to match the styles your website uses? Use your existing class names instead of the presentational grid class names, and remove the classes you don't need. These third party tools are not holy relics - change them to what you need!

    That doesn't mean they can't or don't affect markup inappropriately. I haven't used a grid layout, so I'm not familiar with what's required. But the generated styles aren't inherently bad, they just need to be adapted.

    Published at

    Permalink
  28. Don't Override System Shortcuts

    UPDATE: After deleting and re-cloning RubyAMP, I was able to edit the shortcut. All good!

    I've had to uninstall RubyAMP, a TextMate bundle that I use several times a day, because it overrode Ctrl+B and refused to let me change it.

    I realize most people don't use these shortcuts, so I'll explain it. Ctrl+B moves the cursor back one character, the equivalent of the left arrow key without having to move your hands. My workflow uses Ctrl+B so constantly that I couldn't live with RubyAMP anymore, even though I enjoy its functionality.

    The reason this is so negative is the fact that Ctrl+B is not a shortcut that I added; it's part of OS X. If you are creating keyboard shortcuts for your program or extension, you can't possibly avoid stepping on every other program's toes. But the least you can do is avoid stepping on the operating system's toes. Otherwise it's just negligent, user-hostile design.

    Published at

    Permalink
  29. Thanks, Captain Obvious

    This viral ad for Civilization V about people trying to kick their Civ habit will probably just make you want to pick the game up again.

    Wow, it's almost like that's what they intended.

    Source

    Published at

    Permalink
  30. onclick is Your Friend

    Mark asserts that while using onclick is frowned upon, it's faster and cleaner:

    Sure it makes your XHTML a bit longer, but that’s one less selector jQuery has to parse and one less DOMReady function your browser has to kick off.

    That's true, but it comes at the cost of maintainability. Putting the jQuery example he provided into an external file makes it easier to maintain by having all the JavaScript in predictable and findable places.

    Additionally, that performance hit can be mitigated by using progressive enhancement to give the entire page functionality immediately, then asynchronously loading the JavaScript to enhance it. Asynchronously loading your JS may or may not even be necessary, depending on your JS payload.

    Using inline code like this isn't necessary for most people; they just make your code harder to read.

    Published at

    Permalink
  31. Defending Terrible Things

    You know what would annoy the shit out of me if I were using a screen reader to read a bibliography on the internet? Listening to a goofy-sounding computer voice try to emphasize every single word of Journal of the Study of Obscure and Mostly-in-Latin Canine Diseases Affecting Generally the Respiratory System but Also Sometimes the Lymph Nodes or something. I don’t know that this is still a problem, but the idea is ridiculous in and of itself. Italic does not always mean emphasis, nor does bold always mean MAKE THIS LOUD.

    Hey, you know, that's true. That's what <b> and <i> should be used for. Here's what they shouldn't be used for, from the same article:

    The bold and italic tags are short. The emphasis tag isn’t bad, but the strong tag is way too long to be a good value per keystroke.

    Yeah, semantic HTML uses tags that are longer than one character. <strong> is five whole more letters than <b>! Sounds like someone who throws around a lot of "u"instead of "you", and "y" instead of "why" in textual conversations. Not someone I'd want to take design advice from.

    I add <b> and <i> to things I don’t necessarily want bolded or italicized. I do it because they’re some of the shortest elements available and they provide a hook to style child elements in larger repeated widgets with very precise, complex layouts. If I have to wrap every word of text in a tag just to accomplish some goofy layout, I’m using the smallest thing available.

    That sounds like a pretty poor layout.

    Bottom line is, <b> and <i> have their place, but it's not showing emphasis, nor is it saving keystrokes or because this layout is kinda complicated and I want some easy hooks.

    Published at

    Permalink
  32. Permalink
  33. This is why we can't have nice things.

    The top-voted comments here are a great example of why people are agreeing with John Gruber's assessment of comments.

    What makes DF an efficient and effective soapbox is exactly that it is not noisy. My goal is for not a single wasted word to appear anywhere on any page of the site.

    Those comments distract from the article with fiery, irrelevant rants that show the authors didn't even RTFA. The whole point of the article is lost if you start reading them.

    Published at

    Permalink