tag:blogger.com,1999:blog-27935763481713408572024-03-14T02:52:16.426-07:00Answers to selected problemsThe professional blog of Jonathan Owens, SREintjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.comBlogger24125tag:blogger.com,1999:blog-2793576348171340857.post-25334906823726813342016-06-15T17:05:00.003-07:002016-06-16T15:38:43.924-07:00Have you tried solving the problem?<div>
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;"><i>As delivered at the 2016 New Relic Engineering Offsite</i></span></div>
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;"></span><br />
<div>
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;"><span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;"><br /></span></span></div>
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">
Have you tried trying?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried shouting at it?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried removing the time limit?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried adding a time limit?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried phoning a friend?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried paying someone else to deal with the problem?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried thinking about a different, larger problem instead?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried running toward the problem while screaming?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried the solutions that are only available when no one is looking?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried making the problem into a brand?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried Craigslist?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried politely asking the problem to be less problematic?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried offering the problem the resources it would need to solve itself?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried the soaking pool?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried coding up a universal problem solver?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried fixing things seemingly unrelated to the problem and seeing if the problem mysteriously vanishes?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried re-creating the problem in a different context? How different?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried scoffing at the youthful idealism of those who believe the problem can be solved?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried becoming part of a network of problem solvers so that each problem can be solved by the person most suited to solve it?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried emitting a strangled wail of horror and confusion?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you even tried?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried several partial solutions together?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried making extremely visible preparations for solving the problem, in the hopes that it will be intimidated and flee?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried grepping the codebase for the problem, looking for an explanatory comment?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Was the comment yours?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried making the problem worse? Or was that commit yours?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried solving the problem by giving a lightning talk about creative problem solving methods? </span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried trying to solve the problem accidentally, but accidentally solving it on purpose?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried attempting to lose the problem while unadvisedly racing through a dangerous asteroid field?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried solving the problem... ironically?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried looking at the problem with deep concern until finally breaking the silence with "It's probably the outgroup's fault?"</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried unfollowing the problem?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried hiring the problem to afflict your enemies?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried making the description of the problem into an unutterably powerful spell so people will not talk about it?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried a training montage and climactic final battle with the problem?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried the solution that was inside you all along?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried taking the wizard's advice he gave you in the beginning?</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;">Have you tried... oh, you have? That too? Wow... maybe talk to Ward.</span><br />
<div>
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;"><br /></span></div>
<div>
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif; font-size: 13px; line-height: 17px; white-space: pre-wrap;"><i>Thanks to <a href="http://grognor.livejournal.com/5327.html">Grognor</a></i></span><span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif;"><span style="font-size: 13px; line-height: 17px; white-space: pre-wrap;"><i> and <a href="https://twitter.com/michael_nielsen/status/722867944699486208">Michael Nielsen</a></i></span></span><br />
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif;"><span style="font-size: 13px; line-height: 17px; white-space: pre-wrap;"><br /></span></span>
<span style="color: #333333; font-family: "helvetica neue" , "arial" , sans-serif;"><span style="font-size: 13px; line-height: 17px; white-space: pre-wrap;">For other problem-solving ideas: <a href="http://www.rtqe.net/ObliqueStrategies/">Oblique Strategies</a>, <a href="https://en.wikipedia.org/wiki/TRIZ">TRIZ</a></span></span></div>
intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com1tag:blogger.com,1999:blog-2793576348171340857.post-891272578730404052012-04-30T16:41:00.000-07:002012-04-30T16:43:23.354-07:00How to attend a tech industry conferenceGood search results for guides on how to attend conferences were sorely lacking, so here's my attempt. I recently attended my first work conference, Railsconf 2012 in Austin, TX. Here's a guide based on limited experience and extensive conjecture.<br />
<br />
First, you need to know why you're going:<br />
<br />
<b>If you are paying your own way, your goal is to get hired by a company that will pay to send you next time.</b> You should never have to pay for your tickets to an industry conference if you are gainfully employed in that industry. The good news is, conferences are a great way to skip the HR/Recruiter/cover letter nonsense and talk directly to people with hiring juice.<br />
<div>
<br />
<b>If your company is sending you, your goal is to sell it and by extension, you. </b>Whether you're recruiting, selling your product, or just marketing, your goal is to get your name out there and associate it with good feelings and respect. However, if your company is sending you and you're looking to get hired elsewhere, you'll have to submarine as someone with the first goal. Discretion is advised!</div>
<div>
<br />
<div>
Depending on which of these goals you're after, you're going to have a very different conference.</div>
<div>
<br /></div>
<h4>
Three months before you leave or 1 month before the speaker application deadline, whichever comes first</h4>
<div>
<ul>
<li>Apply to be a speaker. A great way to get Internet Famous in your industry is to give a lot of memorable conference talks. The best way to give a memorable conference talk is to talk about something in your industry that you're passionate about and skilled with. So figure out what that is, make a slide outline, and apply with it. </li>
<li>Lock in your hotel reservation. You want to stay in the conference hotel, both so you can get the room block prices the conference negotiated, and so you can maximize your opportunity for passing interactions with notable people. Room blocks often fill up, so plan ahead. </li>
<li>Start researching flights. <a href="http://blog.penelopetrunk.com/2012/04/27/how-to-travel-for-business-if-you-travel-a-lot/">Don't take the redeye</a>, or take it on the return trip if you must. </li>
<li>Start stalking notable industry companies and likely conference attendees from those companies. Your goal is to make a crib sheet of notable people with short bio and picture, and conversation starters for each one. This might sound creepy, but conferences are intense and you're likely to forget all your research when you're 10 feet away from someone who could <a href="http://blog.penelopetrunk.com/2007/07/09/how-to-start-a-quality-conversation-with-someone-you-dont-know/">change your life with a conversation</a>. </li>
</ul>
<h4>
One month before you leave</h4>
</div>
<div>
<ul>
<li>Check out all the sponsored extracurricular events, including vendor panels, offsites, parties, etc. RSVP as necessary. See above for the crib sheet, these can be great opportunities to ask burning questions and get immediate feedback, or to meet otherwise hard to reach people. At many conferences these are where the real action is, so don't pretend like they're just fluff.</li>
<li>Know your surroundings. Yelp is great for this. You should have at ready hand two restaurants, two bars, and an open-late grocery store within walking distance of your hotel. If you need to take a prospect out for drinks, or find a late night snack, you'll already know where to go. Also scope out local attractions if you're taking extra time to sightsee.</li>
<li>Lock in your flights and other travel needs if you haven't already. Tripit should have everything in it to whatever level of detail you feel comfortable. </li>
<li>If you've applied to speak, you should have received word of your approval by now. It's time to make your presentation sing. There's a <a href="http://www.walkingpaper.org/695">whole</a> <a href="http://www.presentationzen.com/">curriculum</a> <a href="http://www.to-done.com/2005/07/how-to-give-a-great-presentation/">worth</a> of material on how to present well, from which I'll pull two key points: 1) Make great slides by following Zach Holman's <a href="http://zachholman.com/posts/slide-design-for-developers/">example</a>, and 2) practice it till it's polished, with real people. Under no circumstances should you be delivering this talk for the first time at the conference. Practice with your company, your family, and a local interest group until it's easy. Even if you got turned down, do all this anyway. You might get a chance to present it.</li>
<li>If you're going to be looking for a new gig, finish up your resume updating. It should be in great shape and ready to hand to anyone in your industry. Have it in PDF to email immediately, and a few printouts for the old-fashioned types.</li>
<li>Start watching the backchannel traffic (probably on Twitter) for event promotions and notable attendees. </li>
<li>Get a mobile wireless solution, whether phone tethering or a dedicated device. Unless you're going to WWDC, the conference wifi is guaranteed to suck, so have a backup, especially if you're presenting.</li>
</ul>
<h4>
At the conference</h4>
</div>
<div>
<ul>
<li>Mix it up. Be sure to rope strangers into what you're doing, or be interested in what others are up to. You probably know very few people here, and the ones you don't know are<a href="http://en.wikipedia.org/wiki/Mark_Granovetter#cite_note-1"> the most valuable</a>. Invite, ask, repeat.</li>
<li>Be open to schedule change. Conferences are very fluid environments and there's a lot of betting on which talks will be good, but sometimes things don't work out. Don't get married to a schedule, follow the action. </li>
<li>Listen on the backchannel. Most conferences use a Twitter hashtag for discussion. Pay attention and participate, especially if you're speaking. </li>
<li>Take and contribute notes. An iPad or notebook should always be near at hand for jotting down that new project name or Twitter personality.</li>
<li>If you're speaking, market your talk and make the material available immediately after the talk. Use the backchannel and your blog to market it.</li>
<li>Stalk your important people. Parties and booths of prominent sponsors are a good place to find them. Watch activity about them on Twitter, a search can be helpful. Something like '@personality #conferencetag' can be good for sightings.</li>
<li>Wear your swag. Make some if necessary. (Though if you're looking to get hired, avoid your current company's swag.) The name tag isn't nearly enough advertising for what you're about. If you're looking to get hired, make a shirt that says so, or make one with your current venture on it.</li>
<li>Don't get hung over. Conferences are like a treadmill of incoming stuff and opportunities, getting too overextended is a great way to miss things. Plus, you want to be Internet Famous for the right reasons. You aren't really off the clock ever while you're there, because you're representing your name and company even at the bar. </li>
<li>Don't forget to have fun. Networking 18 hours a day will leave you drained. Take time to recharge, whether it be a midday nap, a night where you tuck in early, or just take one of the parties to take off your business hat and enjoy a free cocktail.</li>
</ul>
<h4>
After the conference</h4>
</div>
<div>
<ul>
<li>Follow up. You probably collected several business cards and discovered new personalities. Sync up, follow new people, send notes of thanks to organizers and excellent presenters.</li>
<li>Write a recap and publish it. Be a journalist for others in your industry that couldn't attend, and give some big ideas and highlights.</li>
<li>Take the new knowledge from all the sessions you attended and all the new tools you discovered and make your daily work better. </li>
<li>If you were looking for work, here's where you turn leads into interviews by completing applications and following up with contacts.</li>
<li>If you were selling your wares, here's where you turn leads into sales by closing the loop and reminding them of your conversation. </li>
</ul>
</div>
<h4>
Further Reading</h4>
<div>
Zach Holman's shorter <a href="http://zachholman.com/posts/how-to-survive-tech-conferences/">guide</a>, David Risley's <a href="http://www.davidrisley.com/10-tips-for-attending-tech-conferences/">10 tips</a>, and an OK set of <a href="http://www.businessknowhow.com/growth/conf10.htm">10 others</a> from some PhD. There's a good <a href="http://www.futuresimple.com/blog/make-the-most-of-a-conference/">infographic</a> as well, I like the percentage breakdowns. Here's an <a href="http://runofnetwork.adzerk.com/tales-from-an-ad-salesman/best-practices-for-attending-adtech/">ad-industry one</a> that's good.</div>
</div>intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-68265300784269668342011-10-20T11:42:00.000-07:002011-10-20T23:05:10.417-07:00Steve Jobs is the Tyler Durden of design<div class="separator" style="text-align: center;">
<a href="http://3.bp.blogspot.com/-vQyIAzWQHHg/TqBrKO1tSQI/AAAAAAAAAos/Rp5--0LERus/s1600/original.jpeg" imageanchor="1" style="margin-left: 1em;"><img border="0" height="320" src="http://3.bp.blogspot.com/-vQyIAzWQHHg/TqBrKO1tSQI/AAAAAAAAAos/Rp5--0LERus/s320/original.jpeg" width="225" style='float:left' /></a></div>
<div class="separator" style="text-align: center;">
<a href="http://2.bp.blogspot.com/-DsJG1JpOwA4/TqBq2YebmzI/AAAAAAAAAok/4w_7h29RpPQ/s1600/steve_jobs3.jpg" imageanchor="1" style="float: right; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="http://2.bp.blogspot.com/-DsJG1JpOwA4/TqBq2YebmzI/AAAAAAAAAok/4w_7h29RpPQ/s320/steve_jobs3.jpg" width="185" /></a></div>
<p style='clear: both'>All the ways you wish you could be, that's me. I talk like you want to talk, I control like you want to control, I am smart, capable, and most importantly, I am free in all the ways that you are not.</p>intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-37758238815949033582011-10-12T10:28:00.000-07:002011-10-12T10:28:46.510-07:00Stevey's Google Platforms RantArchiving this here in case it gets deleted. <a href="https://plus.google.com/112678702228711889851/posts/eVeouesvaVX">Accidentally made public from Google+</a>, oh the irony:<br />
<blockquote>
<span class="Apple-style-span" style="font-family: arial, sans-serif; font-size: 13px; line-height: 18px;">Stevey's Google Platforms Rant<br /><br />I was at Amazon for about six and a half years, and now I've been at Google for that long. One thing that struck me immediately about the two companies -- an impression that has been reinforced almost daily -- is that Amazon does everything wrong, and Google does everything right. Sure, it's a sweeping generalization, but a surprisingly accurate one. It's pretty crazy. There are probably a hundred or even two hundred different ways you can compare the two companies, and Google is superior in all but three of them, if I recall correctly. I actually did a spreadsheet at one point but Legal wouldn't let me show it to anyone, even though recruiting <b>loved</b> it.<br /><br />I mean, just to give you a very brief taste: Amazon's recruiting process is fundamentally flawed by having teams hire for themselves, so their hiring bar is incredibly inconsistent across teams, despite various efforts they've made to level it out. And their operations are a mess; they don't really have SREs and they make engineers pretty much do everything, which leaves almost no time for coding - though again this varies by group, so it's luck of the draw. They don't give a single shit about charity or helping the needy or community contributions or anything like that. Never comes up there, except maybe to laugh about it. Their facilities are dirt-smeared cube farms without a dime spent on decor or common meeting areas. Their pay and benefits suck, although much less so lately due to local competition from Google and Facebook. But they don't have any of our perks or extras -- they just try to match the offer-letter numbers, and that's the end of it. Their code base is a disaster, with no engineering standards whatsoever except what individual teams choose to put in place.<br /><br />To be fair, they do have a nice versioned-library system that we really ought to emulate, and a nice publish-subscribe system that we also have no equivalent for. But for the most part they just have a bunch of crappy tools that read and write state machine information into relational databases. We wouldn't take most of it even if it were free.<br /><br />I think the pubsub system and their library-shelf system were two out of the grand total of three things Amazon does better than google.<br /><br />I guess you could make an argument that their bias for launching early and iterating like mad is also something they do well, but you can argue it either way. They prioritize launching early over <i>everything</i> else, including retention and engineering discipline and a bunch of other stuff that turns out to matter in the long run. So even though it's given them some competitive advantages in the marketplace, it's created enough other problems to make it something less than a slam-dunk.<br /><br />But there's one thing they do really really well that pretty much makes up for ALL of their political, philosophical and technical screw-ups.<br /><br />Jeff Bezos is an infamous micro-manager. He micro-manages every single pixel of Amazon's retail site. He hired Larry Tesler, Apple's Chief Scientist and probably the very most famous and respected human-computer interaction expert in the entire world, and then ignored every goddamn thing Larry said for three years until Larry finally -- wisely -- left the company. Larry would do these big usability studies and demonstrate beyond any shred of doubt that nobody can understand that frigging website, but Bezos just couldn't let go of those pixels, all those millions of semantics-packed pixels on the landing page. They were like millions of his own precious children. So they're all still there, and Larry is not.<br /><br />Micro-managing isn't that third thing that Amazon does better than us, by the way. I mean, yeah, they micro-manage really well, but I wouldn't list it as a strength or anything. I'm just trying to set the context here, to help you understand what happened. We're talking about a guy who in all seriousness has said on many public occasions that people should be paying him to work at Amazon. He hands out little yellow stickies with his name on them, reminding people "who runs the company" when they disagree with him. The guy is a regular... well, Steve Jobs, I guess. Except without the fashion or design sense. Bezos is super smart; don't get me wrong. He just makes ordinary control freaks look like stoned hippies.<br /><br />So one day Jeff Bezos issued a mandate. He's doing that all the time, of course, and people scramble like ants being pounded with a rubber mallet whenever it happens. But on one occasion -- back around 2002 I think, plus or minus a year -- he issued a mandate that was so out there, so huge and eye-bulgingly ponderous, that it made all of his other mandates look like unsolicited peer bonuses.<br /><br />His Big Mandate went something along these lines:<br /><br />1) All teams will henceforth expose their data and functionality through service interfaces.<br /><br />2) Teams must communicate with each other through these interfaces.<br /><br />3) There will be no other form of interprocess communication allowed: no direct linking, no direct reads of another team's data store, no shared-memory model, no back-doors whatsoever. The only communication allowed is via service interface calls over the network.<br /><br />4) It doesn't matter what technology they use. HTTP, Corba, Pubsub, custom protocols -- doesn't matter. Bezos doesn't care.<br /><br />5) All service interfaces, without exception, must be designed from the ground up to be externalizable. That is to say, the team must plan and design to be able to expose the interface to developers in the outside world. No exceptions.<br /><br />6) Anyone who doesn't do this will be fired.<br /><br />7) Thank you; have a nice day!<br /><br />Ha, ha! You 150-odd ex-Amazon folks here will of course realize immediately that #7 was a little joke I threw in, because Bezos most definitely does not give a shit about your day.<br /><br />#6, however, was quite real, so people went to work. Bezos assigned a couple of Chief Bulldogs to oversee the effort and ensure forward progress, headed up by Uber-Chief Bear Bulldog Rick Dalzell. Rick is an ex-Armgy Ranger, West Point Academy graduate, ex-boxer, ex-Chief Torturer slash CIO at Wal*Mart, and is a big genial scary man who used the word "hardened interface" a lot. Rick was a walking, talking hardened interface himself, so needless to say, everyone made LOTS of forward progress and made sure Rick knew about it.<br /><br />Over the next couple of years, Amazon transformed internally into a service-oriented architecture. They learned a tremendous amount while effecting this transformation. There was lots of existing documentation and lore about SOAs, but at Amazon's vast scale it was about as useful as telling Indiana Jones to look both ways before crossing the street. Amazon's dev staff made a lot of discoveries along the way. A teeny tiny sampling of these discoveries included:<br /><br />- pager escalation gets way harder, because a ticket might bounce through 20 service calls before the real owner is identified. If each bounce goes through a team with a 15-minute response time, it can be hours before the right team finally finds out, unless you build a lot of scaffolding and metrics and reporting.<br /><br />- every single one of your peer teams suddenly becomes a potential DOS attacker. Nobody can make any real forward progress until very serious quotas and throttling are put in place in every single service.<br /><br />- monitoring and QA are the same thing. You'd never think so until you try doing a big SOA. But when your service says "oh yes, I'm fine", it may well be the case that the only thing still functioning in the server is the little component that knows how to say "I'm fine, roger roger, over and out" in a cheery droid voice. In order to tell whether the service is actually responding, you have to make individual calls. The problem continues recursively until your monitoring is doing comprehensive semantics checking of your entire range of services and data, at which point it's indistinguishable from automated QA. So they're a continuum.<br /><br />- if you have hundreds of services, and your code MUST communicate with other groups' code via these services, then you won't be able to find any of them without a service-discovery mechanism. And you can't have that without a service registration mechanism, which itself is another service. So Amazon has a universal service registry where you can find out reflectively (programmatically) about every service, what its APIs are, and also whether it is currently up, and where.<br /><br />- debugging problems with someone else's code gets a LOT harder, and is basically impossible unless there is a universal standard way to run every service in a debuggable sandbox.<br /><br />That's just a very small sample. There are dozens, maybe hundreds of individual learnings like these that Amazon had to discover organically. There were a lot of wacky ones around externalizing services, but not as many as you might think. Organizing into services taught teams not to trust each other in most of the same ways they're not supposed to trust external developers.<br /><br />This effort was still underway when I left to join Google in mid-2005, but it was pretty far advanced. From the time Bezos issued his edict through the time I left, Amazon had transformed culturally into a company that thinks about everything in a services-first fashion. It is now fundamental to how they approach all designs, including internal designs for stuff that might never see the light of day externally.<br /><br />At this point they don't even do it out of fear of being fired. I mean, they're still afraid of that; it's pretty much part of daily life there, working for the Dread Pirate Bezos and all. But they do services because they've come to understand that it's the Right Thing. There are without question pros and cons to the SOA approach, and some of the cons are pretty long. But overall it's the right thing because SOA-driven design enables Platforms.<br /><br />That's what Bezos was up to with his edict, of course. He didn't (and doesn't) care even a tiny bit about the well-being of the teams, nor about what technologies they use, nor in fact any detail whatsoever about how they go about their business unless they happen to be screwing up. But Bezos realized long before the vast majority of Amazonians that Amazon needs to be a platform.<br /><br />You wouldn't really think that an online bookstore needs to be an extensible, programmable platform. Would you?<br /><br />Well, the first big thing Bezos realized is that the infrastructure they'd built for selling and shipping books and sundry could be transformed an excellent repurposable computing platform. So now they have the Amazon Elastic Compute Cloud, and the Amazon Elastic MapReduce, and the Amazon Relational Database Service, and a whole passel' o' other services browsable at<a class="ot-anchor" href="http://aws.amazon.com/" style="color: #3366cc; cursor: pointer; text-decoration: none;">aws.amazon.com</a>. These services host the backends for some pretty successful companies, reddit being my personal favorite of the bunch.<br /><br />The other big realization he had was that he can't always build the right thing. I think Larry Tesler might have struck some kind of chord in Bezos when he said his mom couldn't use the goddamn website. It's not even super clear whose mom he was talking about, and doesn't really matter, because <i>nobody's mom</i> can use the goddamn website. In fact I myself find the website disturbingly daunting, and I worked there for over half a decade. I've just learned to kinda defocus my eyes and concentrate on the million or so pixels near the center of the page above the fold.<br /><br />I'm not really sure how Bezos came to this realization -- the insight that he can't build one product and have it be right for everyone. But it doesn't matter, because he gets it. There's actually a formal name for this phenomenon. It's called Accessibility, and it's the most important thing in the computing world.<br /><br />The. Most. Important. Thing.<br /><br />If you're sorta thinking, "huh? You mean like, blind and deaf people Accessibility?" then you're not alone, because I've come to understand that there are lots and LOTS of people just like you: people for whom this idea does not have the right Accessibility, so it hasn't been able to get through to you yet. It's not your fault for not understanding, any more than it would be your fault for being blind or deaf or motion-restricted or living with any other disability. When software -- or idea-ware for that matter -- fails to be accessible to<i>anyone</i> for <i>any reason</i>, it is the fault of the software or of the messaging of the idea. It is an Accessibility failure.<br /><br />Like anything else big and important in life, Accessibility has an evil twin who, jilted by the unbalanced affection displayed by their parents in their youth, has grown into an equally powerful Arch-Nemesis (yes, there's more than one nemesis to accessibility) named Security. And boy howdy are the two ever at odds.<br /><br />But I'll argue that Accessibility is actually more important than Security because dialing Accessibility to zero means you have no product at all, whereas dialing Security to zero can still get you a reasonably successful product such as the Playstation Network.<br /><br />So yeah. In case you hadn't noticed, I could actually write a book on this topic. A fat one, filled with amusing anecdotes about ants and rubber mallets at companies I've worked at. But I will never get this little rant published, and you'll never get it read, unless I start to wrap up.<br /><br />That one last thing that Google doesn't do well is Platforms. We don't understand platforms. We don't "get" platforms. Some of you do, but you are the minority. This has become painfully clear to me over the past six years. I was kind of hoping that competitive pressure from Microsoft and Amazon and more recently Facebook would make us wake up collectively and start doing universal services. Not in some sort of ad-hoc, half-assed way, but in more or less the same way Amazon did it: all at once, for real, no cheating, and treating it as our top priority from now on.<br /><br />But no. No, it's like our tenth or eleventh priority. Or fifteenth, I don't know. It's pretty low. There are a few teams who treat the idea very seriously, but most teams either don't think about it all, <b>ever</b>, or only a small percentage of them think about it in a very small way.<br /><br />It's a big stretch even to get most teams to offer a stubby service to get programmatic access to their data and computations. Most of them think they're building products. And a stubby service is a pretty pathetic service. Go back and look at that partial list of learnings from Amazon, and tell me which ones Stubby gives you out of the box. As far as I'm concerned, it's none of them. Stubby's great, but it's like parts when you need a car.<br /><br />A product is useless without a platform, or more precisely and accurately, a platform-less product will always be replaced by an equivalent platform-ized product.<br /><br />Google+ is a prime example of our complete failure to understand platforms from the very highest levels of executive leadership (hi Larry, Sergey, Eric, Vic, howdy howdy) down to the very lowest leaf workers (hey yo). We <i>all</i> don't get it. The Golden Rule of platforms is that you Eat Your Own Dogfood. The Google+ platform is a pathetic afterthought. We had no API at all at launch, and last I checked, we had one measly API call. One of the team members marched in and told me about it when they launched, and I asked: "So is it the Stalker API?" She got all glum and said "Yeah." I mean, I was <i>joking</i>, but no... the only API call we offer is to get someone's stream. So I guess the joke was on me.<br /><br />Microsoft has known about the Dogfood rule for at least twenty years. It's been part of their culture for a whole generation now. You don't eat People Food and give your developers Dog Food. Doing that is simply robbing your long-term platform value for short-term successes. Platforms are all about long-term thinking.<br /><br />Google+ is a knee-jerk reaction, a study in short-term thinking, predicated on the incorrect notion that Facebook is successful because they built a great product. But that's not why they are successful. Facebook is successful because they built an entire constellation of products by allowing other people to do the work. So Facebook is different for everyone. Some people spend all their time on Mafia Wars. Some spend all their time on Farmville. There are hundreds or maybe thousands of different high-quality time sinks available, so there's something there for everyone.<br /><br />Our Google+ team took a look at the aftermarket and said: "Gosh, it looks like we need some games. Let's go contract someone to, um, write some games for us." Do you begin to see how incredibly <i>wrong</i> that thinking is now? The problem is that we are trying to predict what people want and deliver it for them.<br /><br />You can't do that. Not really. Not reliably. There have been precious few people in the world, over the entire history of computing, who have been able to do it reliably. Steve Jobs was one of them. We don't have a Steve Jobs here. I'm sorry, but we don't.<br /><br />Larry Tesler may have convinced Bezos that he was no Steve Jobs, but Bezos realized that he didn't <i>need</i> to be a Steve Jobs in order to provide everyone with the right products: interfaces and workflows that they liked and felt at ease with. He just needed to enable third-party developers to do it, and it would happen automatically.<br /><br />I apologize to those (many) of you for whom all this stuff I'm saying is incredibly obvious, because yeah. It's incredibly frigging obvious. Except we're not doing it. We don't get Platforms, and we don't get Accessibility. The two are basically the same thing, because platforms solve accessibility. A platform <i>is</i> accessibility.<br /><br />So yeah, Microsoft gets it. And you know as well as I do how surprising that is, because they don't "get" much of anything, really. But they understand platforms as a purely accidental outgrowth of having started life in the business of providing platforms. So they have thirty-plus years of learning in this space. And if you go to <a class="ot-anchor" href="http://msdn.com/" style="color: #3366cc; cursor: pointer; text-decoration: none;">msdn.com</a>, and spend some time browsing, and you've never seen it before, prepare to be amazed. Because it's staggeringly huge. They have thousands, and <i>thousands</i>, and THOUSANDS of API calls. They have a HUGE platform. Too big in fact, because they can't design for squat, but at least they're doing it.<br /><br />Amazon gets it. Amazon's AWS (<a class="ot-anchor" href="http://aws.amazon.com/" style="color: #3366cc; cursor: pointer; text-decoration: none;">aws.amazon.com</a>) is incredible. Just go look at it. Click around. It's embarrassing. We don't have any of that stuff.<br /><br />Apple gets it, obviously. They've made some fundamentally non-open choices, particularly around their mobile platform. But they understand accessibility and they understand the power of third-party development and they eat their dogfood. And you know what? They make pretty good dogfood. Their APIs are a hell of a lot cleaner than Microsoft's, and have been since time immemorial.<br /><br />Facebook gets it. That's what really worries me. That's what got me off my lazy butt to write this thing. I hate blogging. I hate... plussing, or whatever it's called when you do a massive rant in Google+ even though it's a terrible venue for it but you do it anyway because in the end you really do want Google to be successful. And I do! I mean, Facebook wants me there, and it'd be pretty easy to just go. But Google is <i>home</i>, so I'm insisting that we have this little family intervention, uncomfortable as it might be.<br /><br />After you've marveled at the platform offerings of Microsoft and Amazon, and Facebook I guess (I didn't look because I didn't want to get <b>too</b> depressed), head over to <a class="ot-anchor" href="http://developers.google.com/" style="color: #3366cc; cursor: pointer; text-decoration: none;">developers.google.com</a> and browse a little. Pretty big difference, eh? It's like what your fifth-grade nephew might mock up if he were doing an assignment to demonstrate what a big powerful platform company might be building if all they had, resource-wise, was one fifth grader.<br /><br />Please don't get me wrong here -- I know for a fact that the dev-rel team has had to FIGHT to get even this much available externally. They're kicking ass as far as I'm concerned, because they DO get platforms, and they are struggling heroically to try to create one in an environment that is at best platform-apathetic, and at worst often openly hostile to the idea.<br /><br />I'm just frankly describing what <a class="ot-anchor" href="http://developers.google.com/" style="color: #3366cc; cursor: pointer; text-decoration: none;">developers.google.com</a> looks like to an outsider. It looks childish. Where's the Maps APIs in there for Christ's sake? Some of the things in there are <i>labs</i> projects. And the APIs for everything I clicked were... they were paltry. They were obviously dog food. Not even good organic stuff. Compared to our internal APIs it's all snouts and horse hooves.<br /><br />And also don't get me wrong about Google+. They're <b>far</b> from the only offenders. This is a cultural thing. What we have going on internally is basically a war, with the underdog minority Platformers fighting a more or less losing battle against the Mighty Funded Confident Producters.<br /><br />Any teams that have successfully internalized the notion that they should be externally programmable platforms from the ground up are underdogs -- Maps and Docs come to mind, and I know GMail is making overtures in that direction. But it's hard for them to get funding for it because it's not part of our culture. Maestro's funding is a feeble thing compared to the gargantuan Microsoft Office programming platform: it's a fluffy rabbit versus a T-Rex. The Docs team <i>knows</i> they'll never be competitive with Office until they can match its scripting facilities, but they're not getting any resource love. I mean, I assume they're not, given that Apps Script only works in Spreadsheet right now, and it doesn't even have keyboard shortcuts as part of its API. That team looks pretty unloved to me.<br /><br />Ironically enough, Wave was a great platform, may they rest in peace. But making something a platform is <b>not</b> going to make you an instant success. A platform needs a killer app. Facebook -- that is, the stock service they offer with walls and friends and such -- is the killer app for the Facebook Platform. And it is a very serious mistake to conclude that the Facebook App could have been anywhere near as successful <i>without</i> the Facebook Platform.<br /><br />You know how people are always saying Google is arrogant? I'm a Googler, so I get as irritated as you do when people say that. We're not arrogant, by and large. We're, like, 99% Arrogance-Free. I did start this post -- if you'll reach back into distant memory -- by describing Google as "doing everything right". We do mean well, and for the most part when people say we're arrogant it's because we didn't hire them, or they're unhappy with our policies, or something along those lines. They're inferring arrogance because it makes them feel better.<br /><br />But when we take the stance that we know how to design the perfect product for everyone, and believe you me, I hear that a lot, then we're being fools. You can attribute it to arrogance, or naivete, or whatever -- it doesn't matter in the end, because it's foolishness. There IS no perfect product for everyone.<br /><br />And so we wind up with a browser that doesn't let you set the default font size. Talk about an affront to Accessibility. I mean, as I get older I'm actually going blind. For real. I've been nearsighted all my life, and once you hit 40 years old you stop being able to see things up close. So font selection becomes this life-or-death thing: it can lock you out of the product completely. But the Chrome team is flat-out arrogant here: they want to build a zero-configuration product, and they're quite brazen about it, and Fuck You if you're blind or deaf or whatever. Hit Ctrl-+ on every single page visit for the rest of your life.<br /><br />It's not just them. It's <i>everyone</i>. The problem is that we're a Product Company through and through. We built a successful product with broad appeal -- our search, that is -- and that wild success has biased us.<br /><br />Amazon was a product company too, so it took an out-of-band force to make Bezos understand the need for a platform. That force was their evaporating margins; he was cornered and had to think of a way out. But all he had was a bunch of engineers and all these computers... if only they could be monetized somehow... you can see how he arrived at AWS, in hindsight.<br /><br />Microsoft started out as a platform, so they've just had lots of practice at it.<br /><br />Facebook, though: they worry me. I'm no expert, but I'm pretty sure they started off as a Product and they rode that success pretty far. So I'm not sure exactly how they made the transition to a platform. It was a relatively long time ago, since they had to be a platform before (now very old) things like Mafia Wars could come along.<br /><br />Maybe they just looked at us and asked: "How can we beat Google? What are they missing?"<br /><br />The problem we face is pretty huge, because it will take a dramatic cultural change in order for us to start catching up. We don't do internal service-oriented platforms, and we just as equally don't do external ones. This means that the "not getting it" is endemic across the company: the PMs don't get it, the engineers don't get it, the product teams don't get it, nobody gets it. Even if individuals do, even if YOU do, it doesn't matter one bit unless we're treating it as an all-hands-on-deck emergency. We can't keep launching products and pretending we'll turn them into magical beautiful extensible platforms later. We've tried that and it's not working.<br /><br />The Golden Rule of Platforms, "Eat Your Own Dogfood", can be rephrased as "Start with a Platform, and Then Use it for Everything." You can't just bolt it on later. Certainly not easily at any rate -- ask anyone who worked on platformizing MS Office. Or anyone who worked on platformizing Amazon. If you delay it, it'll be ten times as much work as just doing it correctly up front. You can't cheat. You can't have secret back doors for internal apps to get special priority access, not for ANY reason. You need to solve the hard problems up front.<br /><br />I'm not saying it's too late for us, but the longer we wait, the closer we get to being Too Late.<br /><br />I honestly don't know how to wrap this up. I've said pretty much everything I came here to say today. This post has been six years in the making. I'm sorry if I wasn't gentle enough, or if I misrepresented some product or team or person, or if we're actually doing LOTS of platform stuff and it just so happens that I and everyone I ever talk to has just never heard about it. I'm sorry.<br /><br />But we've gotta start doing this right.</span></blockquote>intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-40861432755768600472011-10-05T16:01:00.000-07:002011-10-05T16:01:44.107-07:00The Nymwars: Anonymity as a Security StrategyI swore I wouldn't post about this, it was too stupid. The <a href="http://en.wikipedia.org/wiki/Nymwars">Nymwars</a> people were freaking out over nothing, I thought. Just don't use Google+, it's not hard, right? But even within Google there were <a href="https://plus.google.com/u/0/110295984969329522620/posts/ExKJZgBAYxM">fights over it</a>. Then the EFF <a href="https://www.eff.org/deeplinks/2011/07/case-pseudonyms">chimed in</a> and I think they're right, and so does <a href="http://www.jwz.org/blog/2011/08/nym-wars/">JWZ</a>. So I'm pro-pseudonym I guess, because people I like are too.<br />
<br />
But that's not really it, is it? At The City we don't have a "real name policy" as such, but the system makes the assumption that people are going to put their real name and picture on their profile because everyone they interact with using the service is presumably someone they could meet any given Sunday. We have a "nickname" field but that's only to accomodate Dave/David preferences and doesn't hide their real name in a meaningful way. So as a product we have a real name assumption and have steadily resisted hiding real names because the <i>scale</i> is small, and users are naturally bucketed into churches.<br />
<br />
As a church grows, that scale gets bigger, and the probability of someone meeting someone they don't like any given Sunday gets bigger. But on when you put everyone in the church in a flat, searchable namespace on The City, that probability becomes 1 as soon as that someone joins. So we get requests from churches to allow hidden profiles or other privacy measures to make it less obvious that someone with a certain name has an online presence<br />
<br />
We've long dismissed this as a "human sin problem" and simply added ways to limit who can contact them rather than hide their existence. Because The City doesn't do anything for a bad person except show you someone's name, something they could already see anyway using a phone book. But we haven't taken it far enough and I think this debate opens up how. While pseudonymity doesn't really work in the Church, for us, and I think other social sites of appreciable scale, you can choose to require real names if you like. But there had better be <i>complete</i> security controls around displaying presence, and affordance for anonymous interaction where it makes sense.<br />
<br />The exemplar is credit card billing records in a small business. They need to know your real name and billing info and may know your purchase history. Groups of customers may know each other if they choose to announce their patronage, but may choose not to. The business doesn't get to go announcing exactly who purchased from them without raising the ire of their customers, who didn't ask that.<br />
<br />
Real names have <a href="http://en.wikipedia.org/wiki/Names_of_God">power</a>, and we should not expect users to trust us with theirs unless we handle them responsibly. In our case the user may be required to use The City to engage in their church community, and that's a lot to ask in any case. Reducing the fear about engaging in close community online only happens if that community and the people in it are carefully protected.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-10390998400150919752011-09-30T16:20:00.000-07:002013-12-03T17:02:45.937-08:00Rails Migrations Best PracticesThe City is a very long-lived Rails application and as such has accumulated over 600 migrations. We've had enough of them fail in enough interesting ways to learn some rules to follow when writing and applying them.<br />
<br />
<span class="Apple-style-span" style="font-size: large;">One Atomic Change Per Migration</span><br />
<br />
Migrations create a schema version that your database applies atomically. That version should specify <b>one</b> reversible change to the database. Generally speaking this means one <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">add_column</span><span class="Apple-style-span" style="font-family: inherit;">, </span><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">create_table</span><span class="Apple-style-span" style="font-family: inherit;">, or </span><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">add_index</span><span class="Apple-style-span" style="font-family: inherit;"> call per migration. This way if any of your migrations fail you can roll back one and only one migration with a </span><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">db:rollback</span><span class="Apple-style-span" style="font-family: inherit;">. Remember, just because you tested a migration with a dozen </span><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">add_column</span><span class="Apple-style-span" style="font-family: inherit;"> calls doesn't mean it will actually apply in your live site - timeouts, connection errors, and more can all happen and you don't want to have to do SQL tricks to get a half-applied migration to complete. </span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span>
While it is conceptually nice to bundle an entire feature's worth of database changes into its associated migration - say you're adding a <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Story</span> model and you'd like to do something like this:<br />
<br />
<pre>
class CreateStoryTable < ActiveRecord::Migration
def self.up
create_table :stories do |t|
t.string :title
t.integer :journal_id
t.integer :user_id
t.integer :account_id
t.timestamps
end
add_index :stories, [:user_id, :account_id, :journal_id]
add_column :journals, :stories_count, :integer
end
def self.down
remove_index :stories, [:user_id, :account_id, :journal_id]
drop_table :stories
remove_column :journals, :stories_count
end
end
</pre>
This is a <b>bad idea</b>. While it's conceptually consistent, if that <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">add_column</span> call fails, you'll be in a half-migrated state, where the database has been modified but the schema version has not. The database will want to run this migration again the next time you run <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">db:migrate</span>, but guess what? The table is already there, and the migration will fail once again.<br />
<br />
<span class="Apple-style-span" style="font-size: large;">Atomicity Must Be Preserved</span><br />
<br />
Ok, so we have to finish running this migration. You could run a <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">db:migrate:redo</span>, right? Sure, except redo is going to run the down migration, and the first thing it does is remove an index which was never created. You can't move the <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">remove_index</span> line down below the <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">drop_table</span> call because the index won't be there when the table is gone.<br />
<br />
Let's rewrite this migration to do the 3 things it needs to do in separate migrations:<br />
<pre>
class CreateStoryTable < ActiveRecord::Migration
def self.up
create_table :stories do |t|
t.string :title
t.integer :journal_id
t.integer :user_id
t.integer :account_id
t.timestamps
end
end
def self.down
drop_table :stories
end
end
</pre>
<pre>
class AddStoriesIndexOnUserAccountAndJournal < ActiveRecord::Migration
def self.up
add_index :stories, [:user_id, :account_id, :journal_id]
end
def self.down
remove_index :stories, [:user_id, :account_id, :journal_id]
end
end
</pre>
<pre>
class AddStoriesCountToJournals < ActiveRecord::Migration
def self.up
add_column :journals, :stories_count, :integer
end
def self.down
remove_column :journals, :stories_count
end
end
</pre>
<br />
Much better. Now if you deploy this whole set of migrations and need to roll back the entire feature, you can just use <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">rake db:rollback STEP=3 </span><span class="Apple-style-span" style="font-family: inherit;">(if you want, but in the case of this example, you might not have to - see below)</span>, or if any individual one fails you can roll back only the changes it made. <br />
<br />
<span class="Apple-style-span" style="font-size: large;">Code-Safety Within The Release</span><br />
<br />
As much as possible, avoid performing migrations that break compatibility with currently running code. Perform <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">remove_column</span> and <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">drop_table</span> migrations one release after the code change that drops dependency on them. If you're making a structure change that breaks compatibility, you're best off shipping a compatibility change first and migrating later. Pedro Belo at Heroku wrote the <a href="http://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/">definitive guide </a>to this. If you're not as sensitive to scheduled downtime, by all means take it first and ask questions later.<br />
<br />
<span class="Apple-style-span" style="font-size: large;">Time Your Tests</span><br />
<br />
You should of course be testing your releases against production data before deploying them, and when doing so you must verify not only data correctness (<a href="http://blog.carbonfive.com/2011/01/27/start-testing-your-migrations-right-now/">with tests</a> if you can) but change timeliness. If you have a migration that takes 20 minutes with production data and performs breaking changes, you had better know that in advance so you can prepare with either downtime or a staggered release.<br />
<br />
<span class="Apple-style-span" style="font-size: large;">No Code In Migrations</span><br />
<br />
I know the Rails docs say it's ok (<a href="http://guides.rubyonrails.org/migrations.html#using-models-in-your-migrations">with caveats</a>) to use models directly in migrations, but having migrations do too much has been a source of problematic bugs for us in practice. If you have data changes that must be performed as part of the migration, they should be done in a rake task. That way the whole app in its post-migrated state is available to work on, and more importantly, as the size of your dataset grows, you can distribute large dataset transformation operations to a background process like Resque. On a large database, a call like <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Product.all.each {...}</span> could take a very long time, time you could save by parallelizing the work.<br />
<br />
This has its caveats too - it complicates release management by creating a separate task to do just to keep data correct, so YMMV. We've done this out of need because we have so much data.<br />
<br />
<span class="Apple-style-span" style="font-size: large;">No Shortcuts</span><br />
<br />
Migrations, despite their simple appearance, are one of the easiest ways to screw yourself up and lose customer data. Back up before applying them, test and time them carefully, and don't get lazy. Your database is not like code, testing it for correctness and rolling back changes is not easy. Love your users by being rabid about keeping their trust, and they will love you back.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com4tag:blogger.com,1999:blog-2793576348171340857.post-64886556600122784562011-03-04T17:17:00.000-08:002011-03-04T17:17:30.059-08:00Using Amazon EC2 like a cheap, confusing VPSLet's say you wanted a VPS-like server somewhere that runs all the time, gives you root access, and lets you install whatever you want. Let's say you also wanted to attach tons of space, a CDN, a database server, and anything else. Well you'd want to use some of the Amazon AWS products but you'd probably not want to have your VPS service hosted somewhere else, so you can centralize. <br />
<br />
So you'd think, EC2 lets you run servers, and EBS lets you use them like they have regular hard drives attached, let's just buy a yearlong EC2 reservation and get a fat server for an amortized $75/month! It's a great plan, until you start to try it and realize everything in AWS is designed in little tiny pieces, not systems. <br />
<br />
So then you try something like RightScale, or Judo, or Scalr, and you think, I don't need this autoscaling clouding magic scripty crap, I just want a server! Is that so hard? Well no, but it's $250/month from Slicehost, and you don't want to pay that.<br />
<br />
Let's do something different. Let's get as close to a VPS as we can in EC2 using the simplest tools possible - just the <a href="https://console.aws.amazon.com/ec2/">console</a> and the <a href="http://docs.amazonwebservices.com/AmazonEC2/gsg/2006-06-26/setting-up-your-tools.html">ec2 API tools</a>.<br />
<br />
First, some concepts. AWS has its own language that is not very familiar. I strongly recommend taking an hour or two and reading the <a href="http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/">EC2 User Guide</a> so you can get a handle on what's going on here. I tried to avoid it but really it's best to just read it straight through. This isn't a quickie project.<br />
<br />
The core units we'll be focusing on are EBS volumes and EBS-backed AMIs. These are going to form the core of your server's identity.<br />
<br />
EBS volumes can start as either a Snapshot, which is basically a tape backup of a drive at a point in time, or as an empty drive. Once created, they can then exist either as a detached ("available") volume, which is just like an unplugged hard drive sitting on the table, or an attached ("in-use") volume, which is like a drive plugged into a server. (Note that the server they are attached to doesn't have to be running.) You can leave EBS volumes sitting around detached as long as you like, and take snapshots of them whenever. <br />
<br />
EBS-backed AMIs are awesome, because we can take any Snapshot and make it the root device of an AMI. Then whenever you start the AMI, it would be like like taking that Snapshot (remember, tape backup), buying a server, copying the tape data onto the server's main hard drive, and starting it up. You still have the tape, and the server now has the tape data (as copied to its hard drive, a new EBS volume) as its starting point. <br />
<br />
The thing about AMIs is they don't really exist as servers, they exist as the idea of a server. Sort of a specification, not a saved state. They have a particular set of disks (or snapshots) to attach, a kernel to run, and an architecture, but that's about it. In most cases they're designed to destroy all the data they create during their lifetime, because AWS likes you to run things on-demand, not forever. Well in a VPS you want forever, so we're going to do some tricks to get there.<br />
<br />
What we're going to create in AWS is an AMI that, when you create an instance from it, creates and boots from an EBS volume containing the data in a Snapshot. When the instance is stopped (shut down) or terminated (deleted), the EBS volume it made will sit there detached in your list. <br />
<br />
This is important because while you could use an EBS-backed instance as a VPS simply by never terminating it once it's running, if for some reason it was terminated, you'd lose all the data on it unless you took a snapshot, but even then you'd be restoring from your last snapshot, not the moment the instance stopped. This is why termination protection exists, but a checkbox on a webpage is not enough to protect production data.<br />
<br />
Let's do some work. Look for a the AMI you want in the <a href="https://console.aws.amazon.com/ec2/home?region=us-east-1#s=Images">AMI list</a>. At the time of writing <tt>ami-3202f25b</tt> (Ubuntu 10.04 20110201.1) is a good one. Launch it, and go to <i>Instances</i>. When it's running, right-click it and select <i>Create Image (EBS AMI)</i>. You'll see an AMI go to Pending in your list of AMIs Owned By Me, and a Snapshot go Pending as well. Get some paper and write down the Snapshot ID, then go back to AMIs and write down the Kernel ID.<br />
<br />
Now drop into your terminal where we'll be using some of the ec2 tools (you installed them, right?). You can't do what we want from the web console, which is to set up a server that keeps all its disks around when you terminate it. Use <tt>ec2reg</tt> like so:<br />
<blockquote><code>ec2reg -n 'Ubuntu 10.04 20110201.1 Base' -d 'Basic ubuntu server configuration' --root-device-name /dev/sda1 -b /dev/sda1=<i>your-snap-id</i>:8:false -a x86_64 --kernel <i>your-kernel-id</i></code></blockquote>What you just did was make an AMI (again, the <i>idea</i> of a server) that runs Ubuntu 10.04. What's special about it is that when you make instances out of it, those instances don't destroy the data they made over their lifetime. The <tt>false</tt> at the end of the <tt>-b</tt> argument is the trick.<br />
<br />
So how many servers do you want? Just launch as many of those AMIs as you like and even if you terminate them, the stuff they do will still exist. You probably never actually <i>want </i>to terminate them, only stop them, but you would be safe even if you did. Now you can safely install software right on your instances without running launch scripts or pasting in a bunch of userdata.<br />
<br />
Once you start running your instances, you'll still want to take snapshots of their volumes every so often. As long as you don't terminate your instances, only stop them, they'll work just like servers with hard drives. If you do terminate one, you'll need to do a little runaround. You'll have a detached volume that was that instance's root device. You can't boot a server from a detached EBS volume, only a snapshot. So take a snapshot of the detached root device, then run <tt>ec2reg</tt> again with that snapshot as the snap-id. You now have a new 'rescue' AMI that really only represents that one particular instance, so when you launch a replacement instance from it, you should be right back where you left off. You can keep that AMI around if you want, the important thing is the snapshot. You can always create another AMI based on a standing snapshot that has the data you want, just make sure you pick the right kernel and mountpoints. Documentation helps here. <br />
<br />
So this is great for software on a server's root drive, but let's say you want to run a data storage engine on your VPS-like EC2 setup. You wouldn't be getting much of the benefits out of EC2 by having all that data stored right on the boot drive, far better to use a separate EBS volume for your data, or even a couple of them RAIDed together. This allows you to take data snapshots separately from system snapshots.<br />
<br />
There's 2 ways you could do this. First, we'll design a setup where all the instances you want will have a data volume mounted when they are launched.<br />
<br />
We'll need to make a different AMI to represent this, because you can't edit the launch block device configuration of an AMI once you've made it. We'll use <tt>ec2reg</tt> again:<br />
<blockquote><code>ec2reg -n 'Ubuntu server with 10GB at sdf1' -d 'Ubuntu 10.04 with new 10GB volume at /dev/sdf1' --root-device-name /dev/sda1 -b /dev/sda1=<i>your-snap-id</i>:8:false -b /dev/sdf1=:10:false -a x86_64 --kernel <i>your-kernel-id</i></code></blockquote>This will create a new 10GB EBS volume at <tt>/dev/sdf1</tt> when this AMI is launched. It won't actually be mounted in the OS, or even formatted, because the root device snapshot we're working with has no idea that it exists. If you're launching these for the first time this should be fine, because you can format it, declare it in <tt>fstab</tt>, etc. and your changes will be safe due to the persistent root device. If you terminate an instance and have to reattach its snapshot to a new AMI later, you'll need to snapshot both the root and data volumes and include them in the rescue AMI:<br />
<blockquote><code>ec2reg -n 'Redis slave server rescue AMI' -d 'Rescuing the redis slave from snapshots' --root-device-name /dev/sda1 -b /dev/sda1=<i>instance-snap-id</i>:8:false -b /dev/sdf1=<i>instance-data-snap-id</i>:10:false -a x86_64 --kernel <i>your-kernel-id</i></code></blockquote>This will make an AMI that will let you relaunch that instance with both its root device and its data intact.<br />
<br />
The other way to do this is to simply create some EBS volumes and attach them to the instances you made from the first AMI. They won't be destroyed if you terminate the instances because they were attached after the instances launched. Just don't forget to reattach them if you have to rescue the instances, or include them in the rescue AMI as shown above.<br />
<br />
Taking into account the costs of data transfer, snapshot and EBS storage, and instance type runtime costs, this may or may not actually be cheaper than a standard VPS. You get a lot more headroom though, and easy integration with other AWS products.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com3tag:blogger.com,1999:blog-2793576348171340857.post-83966751611759992522011-02-25T10:38:00.000-08:002011-02-25T10:38:05.660-08:00Rails routing gotcha: Don't name a route not_foundWhen moving our app to Bundler, I got the following error:<br />
<br />
<blockquote style="font-family: "Courier New",Courier,monospace;">(__DELEGATE__):2:in `not_found': wrong number of arguments (2 for 0)<br />
(ArgumentError)<br />
from (__DELEGATE__):2:in `send'<br />
from (__DELEGATE__):2:in `not_found'<br />
from /app_dir/vendor/rails/activesupport/lib/active_support/<br />
option_merger.rb:20:in `__send__'<br />
from /app_dir/vendor/rails/activesupport/lib/active_support/<br />
option_merger.rb:20:in `method_missing'<br />
from /app_dir/config/routes/plaza_routes.rb:5<br />
from /app_dir/vendor/rails/activesupport/lib/active_support/core_ext/<br />
object/misc.rb:78:in `with_options'<br />
from /app_dir/vendor/rails/actionpack/lib/action_controller/routing/<br />
route_set.rb:51:in `namespace'</blockquote><br />
The secret was a route that looked like this:<br />
<br />
<blockquote style="font-family: "Courier New",Courier,monospace;">global.not_found '/not_found', :controller => 'home', :action => <br />
'not_found'</blockquote><br />
The solution? Rename the route to something else. No idea why this wasn't exposed before, but at least there's a solution!intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-78693468868623976482009-09-09T16:44:00.000-07:002009-09-09T16:50:33.098-07:00Cisco VPN vs. Parallels 4.0If you're using Cisco VPNClient on OS X 10.5, and you have Parallels 4.0 installed, you may be treated to the following error when attempting to start a VPN connection over ppp - a typical use case for an on-call developer using a 3G modem like the USBConnect Mercury:<br /><br /><blockquote><br />305 10:28:16.787 05/22/2008 Sev=Warning/2 CVPND/0x83400011<br />Error -28 sending packet.<br /><br />...<br /><br />Output size mismatch. Actual: 0, Expected: 237. (DRVIFACE:1319) </blockquote><br /><br />The fix for this is to uninstall Parallels and buy VMWare fusion instead because Parallels is slow and it sucks. If there's a workaround I don't have the patience to find it.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-18821583745816134152008-06-27T16:15:00.000-07:002008-06-27T16:16:46.774-07:00More on practiceIn a <a href="http://www.codinghorror.com/blog/archives/001138.html">great post</a> by Jeff Atwood.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-101579813416512522008-06-22T11:31:00.000-07:002008-06-23T21:50:02.987-07:00Ada Byron was HomeschooledContinuing this discussion from <a href="http://www.geeknews.net/2008/06/21/who-needs-a-computer-science-degree-when-theres-wikipedia">Geeknews</a> and <a href="http://greatjustice.info/university-versus-self-taught/">greatjustice</a>.<br /><br />The post at geeknews doesn't make much of a statement except in the title, but since this is a generally inflammatory topic, they've earned a pile of readership from that alone. And to prove I'm not the guy with the degree trumpeting the wonders of <a href="http://www.theamericanscholar.org/su08/elite-deresiewicz.html">elitism</a>, I went to a second-place state college. Even that was a better education than I could've managed on my own, but then it doesn't take MIT to do that.<br /><br />I've been a vocal supporter of bootstrapping your way into other careers for a couple years now, but recently have changed my mind. I think the question of degree vs. hard knocks goes deeper than the "value of an education" or "real-world experience". Those are great things, but what will really turn a journeyman to a master is <a href="http://projects.ict.usc.edu/itw/gel/EricssonDeliberatePracticePR93.pdf">deliberate practice</a> (PDF).<br /><br />According to Ericsson, et. al., to really be deliberate practice, not just any task will do: <blockquote>The most cited condition [for optimal improvement] concerns the subjects' motivation to attend to the task and exert effort to improve their performance. In addition, the design of the task should take into account the preexisting knowledge of the learners so that the task can be correctly understood after a brief period of instruction. The subjects should receive immediate informative feedback and knowledge of results of their performance. The subjects should repeatedly perform the same or similar tasks.</blockquote>This probably sounds obvious, but take a step back. What they're asserting is that <em>learning is doing</em>. It is a <em>task</em>. You can sit on your ass reading CS articles on Wikipedia for 4 years and never learn how to write a program. You could even read through MIT's "Structure and Interpretation of Computer Programs" <a href="http://ocw.mit.edu/OcwWeb/Electrical-Engineering-and-Computer-Science/6-001Spring-2005/CourseHome/index.htm">course material</a> for free and not learn a damn thing about Scheme. <br /><br />Whether you're learning from Wikipedia, a professor, a colleague, or a book, you're not going to start learning till you care about the subject and start stretching. A great programmer is one that is not afraid to do just that, and at the end of the day it doesn't matter whether he went to college or not. He will be great because he wanted to be and was not afraid to do what it takes to get there: intentional, deliberate, ongoing practice. <br /><br />This practice of learning has been around much longer than universities. The system of apprenticeship survived for thousands of years. Any good tutor can create the above conditions for their student, but at the end of the day it is the student that must put the <em>work</em> in, not just the reading. Or the student can put that work in even without a tutor, though it will take much more effort.<br /><br />So when hiring, look for the curious, unafraid, diligent one. Don't let a degree fool you into thinking that, just because they had the opportunity to learn, they did anything but read.<br /><br />Further reading: A <a href="http://weblog.raganwald.com/2008/01/no-disrespect.html">Reg Braithwaite post</a> in a similar vein.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com3tag:blogger.com,1999:blog-2793576348171340857.post-7729048752920277572008-03-10T22:09:00.001-07:002008-03-10T22:59:51.690-07:00On EleganceLately I've been working on a new media serving backend for Treemo. We've outgrown the features on our existing setup, and since the vast majority of our hits are content downloads, it's time to optimize those.<br /><br />One thing that we've discovered needs changing is the URL structure for our content. Most large content-focused sites like Flickr, Fotolog, or Imageshack use an unguessable portion of the URL to enforce visibility restrictions. This allows easy connection to a CDN, as you can simply pass out those URLs to a hosting service secure in the knowledge that they will only be presented to the appropriate people. Livejournal is an exception, they do authorization and byteserving on their balancers. We copied this setup initially, but as is the case with many Livejournal tools, we found we had to really go whole hog copying the rest of their setup (in this case, perlbal) to make it useful. So we're changing our approach.<br /><br />Obviously changing your URL scheme for embedded content is a rather major migration, and not to be done lightly. Among the many issues that have arisen during this process is what the URLs should look like. I spent a lot of time evaluating the Flickr URL scheme, as they're the biggest and we were looking to model their architecture instead of Livejournal's this time. One thing I came to appreciate about their scheme is the elegance. You can check it out here:<br /><br />http://www.flickr.com/services/api/misc.urls.html<br /><br />The longer I worked on various combinations of schemes that would give us the structure we wanted, the more I realized that their scheme included everything you needed and nothing you didn't, and did so in under 80 characters. Making something that nice or nicer became a goal. <br /><br />I got to the point of evaluating how to alter our object model to accommodate the new information that the filesystem would need to back up the new scheme, and darn if it wasn't hard. Adding the data I wanted was going to be expensive. There were easier schemes I could use involving long hashes or other things that would be just as secure, just as functional. But they'd be huge. And something in me didn't like that, and a few halfhearted dust-ups with my officemates found me unable to articulate why. <br /><br />Part of it felt like well, Flickr has already proved this scheme works. They've learned lessons about it that we don't haven't, so let's not reinvent the wheel here. But that doesn't get me very far. Other, easier schemes would work, and who cares what the URLs look like? <br /><br />And I couldn't escape the fact that I do, and I had to leave it at that.<br /><br />But the more I thought about it, stronger I felt that not only should a new scheme be functional, but it should be <i>nice</i>. It should bear the mark of a system made with care for <i>people</i>, not tools. I think the ability to make this small part of the system exceed requirements in an elegant and surprising way became important, because if all I do is make tools that work, I get bored. Anyone can write code that does what the customer wants. The magic happens when we make code the customer doesn't realize he wants. You might not realize that nice URLs make the system more humane to use, but when they're obnoxious, it's noticed. "Huh," your users will say, "that's funny-looking." Not a thought that should be going through your users' minds, even for the half-second their subconscious will think about it. <br /><br />I think we as developers have a lot of responsibility to think like a user even - maybe even especially - when it makes our jobs harder. Thinking for them helps us develop products that exceed their expectations, which allows teams to gel, which is the only way great products are made.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-8773241353631198202008-02-05T11:15:00.001-08:002008-02-05T11:15:44.489-08:00PHP in a nutshell# is_writable — Tells whether the filename is writable<br /># is_writeable — Alias of is_writableintjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-28425390756264864082007-12-18T14:58:00.000-08:002007-12-18T20:39:53.480-08:00On Moving, Part 3<span style="font-size:130%;">Arrival</span><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.treemo.com/users/niralisse/channel/item/235942"><img style="margin: 0pt 10px 10px 0pt; float: right; cursor: pointer; width: 200px;" src="http://www.treemo.com/files/treemo.niralisse.235942.mm.jpg" alt="" border="0" /></a><span style="font-size:100%;">We've been working toward this move in parallel with our next major treemo.com release. The two coincide so closely that today, on our second day in the building, we're shipping our Biggest Release Ever.<br /><br />One of the key considerations when performing such a trick is to keep productivity up while adjusting to a new space. The best way to do that is to make sure that when you arrive, everything is ready to go. That we managed this is remarkable, and I'm still kind of taken aback. When I arrived on Monday there was little to do except set up my workstation, plug in, and get to work. We had our first meeting in the new conference room to plan the release, and got to doing it. Today, we had lunch delivered and camped out smashing bugs. I got some delicious cider from my sister, who works in a coffeeshop on the first floor.<br /><br />We are so spoiled. :)<br /><br /><br /></span>intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-74099585951860512332007-12-17T11:01:00.000-08:002007-12-18T20:40:30.709-08:00On Moving, Part 2<span style="font-size:130%;">Departure</span><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.treemo.com/users/niralisse/channel/item/235244"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 200px;" src="http://www.treemo.com/files/treemo.niralisse.235244.mm.jpg" alt="" border="0" /></a><span style="font-size:100%;">Here are some things we did that worked when leaving our space in Ballard:<br /></span><ul><li>Sent contingents from each team to the candidate office spaces</li><li>Put our office manager <span style="font-style: italic;">really</span> in charge of the moving details and scheduling<br /></li><li>Had everyone fill out the office map in the way they felt best and combined them to create the final layout</li><li>Allowed our IT team to spend plenty of time readying the infrastructure move</li><li>Hired a mover (we didn't do that the first time...)</li><li>Bought everybody FlexPasses to compensate for the loss of parking</li><br /></ul>Mercifully, I was insulated from most of the thousands of decisions and jobs to be done when moving 10 people's workspace. All I had to do was pack up my things and unplug my workstation. It seems like that's exactly right for what I need to do for my job. And after so much time here having to worry about everything, it was a nice change.<br />I'm certain that others in the company were frantically busy trying to get everything moved, and obviously they did a wonderful job. I love it when a plan comes together.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com1tag:blogger.com,1999:blog-2793576348171340857.post-53497676371643039752007-12-15T23:55:00.000-08:002007-12-18T20:42:00.048-08:00On Moving, Part 1<span style="font-size:130%;">Promise</span><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.treemo.com/users/niralisse/channel/item/233602"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 200px;" src="http://www.treemo.com/files/treemo.niralisse.233602.mm.jpg" alt="" border="0" /></a><p>The space in which you work completely defines the parameters of your activity there. It outranks even your CEO by placing hard physical boundaries around what types of work can and cannot be done. Work which requires quiet cannot be done in an environment full of noise. Work which requires collaboration cannot be done in a small room. Work which requires creativity will suffer in a dreary environment, and thrive in a beautiful one.</p><p>On Monday we will have the opportunity to discover what kind of work we can do in a large, window-rich, cosmopolitan downtown office space. Downtown! The lights! The glamor! New restaurants! Bus passes!</p><p>At every stage in our growth we have bristled against too-small office space that failed to meet our needs. Not enough privacy, not enough rooms, not enough wiring, not enough light, something. I want to code, and someone's one cube over arguing about a feature. I need to have a conference call, but have no door. I need to pee, but there's only one bathroom for 12 of us. I want to see if it's raining, but I have to hit F12 because the window is down the hall. I want to sketch a brilliant design, but the wall's too small for a decent whiteboard. I want to work 8 hours and wonder where the time went, but the overhead lighting gives me a headache before noon, and this desk is too small to put my coat, notepad, and lunch on it all at the same time. I wonder if Google would give me a bigger desk?<br /></p><p>Obviously when you're a startup, even a funded one, you can't afford <a href="http://seattletimes.nwsource.com/html/businesstechnology/2004005458_microsoftbuilding10.html">Building 99</a>. But you do have to strike a balance, because every millisecond I spend thinking one of the above thoughts, I'm not writing great code.</p>But! Downtown! Here's some of the things our new space enables us to do:<br /><ul><li>Allow our receptionist to greet visitors immediately at a striking entry desk<br /></li><li>Hold a meeting in a real conference room<br /></li><li>Enjoy our view of Westlake plaza from any point within the office</li><li>Retire to a secluded space to be left undisturbed when necessary</li><li>Write on the half-glass walls with whiteboard pens</li><li>Put huge desks in the offices</li><li>Work from home over a real VPN</li><li>Step out the elevator doors to the middle of downtown, anytime we want</li></ul>All of which are things that enable us to spend less time thinking about the stuff we can't do, and more time doing what we want to do - making great software.<p></p>Tomorrow, Part 2: Moving Dayintjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-1904946257474535122007-11-23T23:50:00.000-08:002007-11-24T00:04:08.639-08:00Bill Gates on focused design<blockquote>"The finest pieces of software are those where one individual has a complete sense of exactly how the program works. To have that, you have to really love the program and concentrate on keeping it simple, to an incredible degree."</blockquote><blockquote>"We're no longer in the days where everything is super well crafted. But at the heart of the programs that make it to the top, you'll find that the key internal code was done by a few people who really know what they were doing."</blockquote><br />Unfortunately for Bill, his company is having a difficult time getting Windows back to something which resembles David Cutler's locomotive of an OS, which was itself birthed in a rather <a href="http://www.amazon.com/Show-Stopper-Breakneck-Generation-Microsoft/dp/0029356717">chaotic manner</a>. How disappointing to see his company forget these essential truths in their core products over the years. I think for them to compete with Apple or Google the way they want to, they're going to have to get back to this. Iconoclasm is something they've forgotten how to do well, and they're getting their asses handed to them for it.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-63253393266446281582007-11-17T21:33:00.000-08:002007-12-08T00:29:02.077-08:00Abstraction Layers, Change Streams, and SilosBeing a developer, I tend to see people organization through the same lens as systems organization.<br /><br />In a software system, every interface forms an abstraction layer. Every developer working within that system designs APIs, as this <a href="http://video.google.com/videoplay?docid=-3733345136856180693">helpful video </a>points out. Good APIs have the qualities outlined in that video, that of being:<br /><ol><li>Easy to learn<br /></li><li>Easy to use, even without documentation<br /></li><li>Hard to misuse<br /></li><li>Easy to read and maintain code that uses it<br /></li><li>Sufficiently powerful to satisfy requirements<br /></li><li>Easy to evolve<br /></li><li>Appropriate to its audience<br /></li></ol><br />The first five items on the list are best accomplished by ensuring that the API presents a <span style="font-style: italic;">consistent level of abstraction.</span> All its functionality should address the same set of concepts. We'll pick on PHP's Iterator interface:<br /><pre><br />next()<br />prev()<br />valid()<br />rewind()<br /></pre>All these functions deal precisely with items in an iterable object. An example of an API that didn't do this might look like the following:<pre><br />$user->getHeldItems()<br />$user->holdItem()<br />$user->syncProfileData()<br />$user->checkoutItems()<br /></pre>If you've spent more than 10 minutes of your life writing code, that third method should fall with a loud <span style="font-style: italic;">thunk </span>.<br /><br />Making an API easy to evolve starts with change stream encapsulation. A change in the details of implementation can generally be ignored by higher-level consumers of the API. So over time, as that method evolves, its change stream will be hidden by its interface.<br /><br />Organizations must also create the same abstractions, because nobody has the time to keep track of every single thing in the organization. Our office manager needs to not care how the DHCP server is configured, she just needs to know she can get to the Ikea site and get us some desks. Anything else is a distraction. For a developer, this is writ quite large, as the tools I require are typically much more complicated than a web browser. Maintaining groupware like Exchange, for example - not trivial. Babysitting a build process is another common one. I need that stuff to work so I can write good code, but I must spend almost none of my time doing it. So teams get created, presenting an organizational communication interface ("OPI"?).<br /><br />And that's all well and good, but what happens when that boundary lies across a layer of the software? For example, when I rely on a RewriteRule set in Apache by our sysadmins? I have to communicate across a layer of both organizational <span style="font-style: italic;">and</span> application-layer abstraction. And that presents a problem, because any organization-layer abstraction necessarily restricts the clarity of communication. I don't necessarily work with our sysadmin on Apache most of the time, so I don't have a clear idea of how it intertwines. That's a problem when I'm trying to design something that might interact with that subsystem.<br /><br />So to fight this, organizational abstraction layers need to have the same careful consideration given them as a good API, and meet all 7 of the qualities listed at the top. Everything from process methodologies to groupware have to be evaluated in this way, because to solve the communication problem that <span style="font-style: italic;">is</span> software, the ideas crossing those boundaries should be as fluent as possible.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-22230767829162221732007-11-12T22:19:00.000-08:002007-11-12T22:42:16.388-08:00Interview pet peeves for today<p>Really know the language that the company you are interviewing for uses. Don't think you can pull it out of your ass cause you worked in it 3 years ago. Languages change a lot. I don't have time to teach you how to deal with Exceptions in PHP 5, because frankly you aren't worth it if you can't do some homework before you come in for an interview. This goes doubly so if the posting says "PHP Developer" <a href="http://seattle.craigslist.org/see/sof/467040330.html">right in the title</a>.<br /></p><p>I know we're your fifth interview this week, and frankly I don't care. I get 60 minutes to evaluate whether to give you commit access to my code, code I've spent the last year preening over and perfecting. Separately, I have to evaluate whether I want to be stuck in a room with you for 8 hours a day, potentially for the next several years. You better be on your best behavior, and you better want it, and you better be excited, because if you come in here looking like you're just looking for something to do or a buck to make, you're going right back out the door. But if you want to <span style="font-style: italic;">create</span>, if you want to make something <span style="font-style: italic;">beautiful</span>, you'll be on my short list of favorite people, people on whom I'm going to tell my boss to spend a lot of money.<br /></p><p>Have a passion that isn't code. It's not a dealbreaker if you don't, but I really like to see people with multi-disciplined lives. Back when you applied for college, there was a box labeled "Extracurricular activities". If you left that box empty back then, you should try and fill it soon, because it's there for a reason. Musicians write more interesting code. They see patterns I don't. Foodies know where all the good restaurants are. Gamers bring in their xboxes. Diversity in a small office is vital, because we're all going to spend more time here than at home, and if all we do is our jobs, we're going to run out of things to talk about real fast.</p>intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com6tag:blogger.com,1999:blog-2793576348171340857.post-70548325644058058542007-11-11T23:22:00.000-08:002007-11-12T22:18:44.808-08:00Extending Iterator for fun, profit, and hasslesPHP's built-in <a href="http://us.php.net/manual/en/language.oop5.iterations.php">Iterator class</a> is lovely, as is the <a href="http://us.php.net/manual/en/ref.spl.php">SPL's overcomplicated interpretation</a>. But as soon as you want to do something interesting, like say, go to the previous item in an object, both these implementations fall flat. That's right, you can't call $iterator->prev() on ANY SPL Iterator classes, because PHP's built-in Iterator doesn't have it! This seems to me a major oversight, especially considering the array functions that power most Iterator implementation have it <a href="http://us.php.net/manual/en/function.prev.php">right there</a>. <br /><br />The other thing you can't do is focus an iterator on a particular item in it. This is more understandable, as arrays have no concept of this. Iterable objects should though.<br /><br />So here at Treemo, we use lots of list objects. Lists of content, users, comments, etc. etc. Lists, obviously, should be iterable, but we'd like to be able to navigate in <span style="font-style:italic;">both</span> directions, and focus on objects we're interested in.. So we cooked up a base class that does all its Iterator management by hand, because the only way to get this functionality cleanly is to do everything yourself. Because nobody should EVER have to do this twice, and because PHP didn't do it for me the way it ought to have, I'm gifting it to you.<br /><pre><br />/**<br /> * A modifiable list object<br /> * <br /> * @package Core<br /> */<br />class ObjectList implements Iterator<br />{<br /> /**<br /> * The internal array backing for the list<br /> *<br /> * @var Array<br /> */<br /> protected $items;<br /> <br /> /**<br /> * The current index of the list.<br /> *<br /> * @var int<br /> */<br /> protected $key;<br /><br /> /**<br /> * Create the list from either nothing or an array of existing items.<br /> *<br /> * @param Array $items<br /> */<br /> public function __construct ($items = null)<br /> {<br /> if (! is_null($items))<br /> {<br /> $this->items = $items;<br /> } else<br /> {<br /> $this->items = array();<br /> }<br /> }<br /><br /> /**<br /> * Focus the list on its first element.<br /> */<br /> public function rewind ()<br /> {<br /> $this->key = 0;<br /> }<br /><br /> /**<br /> * Get the next item in the list.<br /> *<br /> * @return Object<br /> */<br /> public function next ()<br /> {<br /> if (array_key_exists($this->key + 1, $this->items))<br /> {<br /> return $this->items[++ $this->key];<br /> } else<br /> {<br /> $this->key = count($this->items);<br /> return false;<br /> }<br /> }<br /><br /> /**<br /> * Get the previous item in the list.<br /> * <br /> * @return Object<br /> */<br /> public function prev ()<br /> {<br /> if ($this->key < 0)<br /> {<br /> return false;<br /> }<br /><br /> return $this->items[$this->key--];<br /> }<br /><br /> /**<br /> * Focus the list on a particular object.<br /> *<br /> * @param object $item the needle<br /> * @return object or FALSE if the object was not found.<br /> */<br /> public function focus ($item)<br /> {<br /> $k = array_search($item, $this->items);<br /> if ($k !== false)<br /> {<br /> $this->key = $k;<br /> return $this->items[$this->key];<br /> }<br /> return false;<br /> }<br /><br /> /**<br /> * Focus the list on its last item, and return it.<br /> *<br /> * @return object<br /> */<br /> public function end ()<br /> {<br /> $this->key = count($this->items) - 1;<br /> return $this->items[$this->key];<br /> }<br /><br /> /**<br /> * Return the item that the list is currently focused on, or FALSE if it is<br /> * focused beyond the end of the list.<br /> *<br /> * @return object<br /> */<br /> public function current ()<br /> {<br /> return (isset($this->items[$this->key]) ? $this->items[$this->key] : false);<br /> }<br /><br /> /**<br /> * Return the current list index.<br /> *<br /> * @return int<br /> */<br /> public function key ()<br /> {<br /> return $this->key;<br /> }<br /><br /> /**<br /> * Return the status of the lists' index. Is it between 0 and the end of the list?<br /> *<br /> * @return bool<br /> */<br /> public function valid ()<br /> {<br /> return ($this->key >= 0) && ($this->key < count($this->items));<br /> }<br /><br /> /**<br /> * Returns the length of the list.<br /> *<br /> * @return int<br /> */<br /> public function count ()<br /> {<br /> return count($this->items);<br /> }<br /><br /> /**<br /> * Append an object onto the end of the in-memory list.<br /> *<br /> * @param object $newObject<br /> * @return int 1 for success, 0 for failure.<br /> */<br /> public function append ($newObject)<br /> {<br /> return array_push($this->items, $newObject);<br /> }<br /><br /> /**<br /> * Prepend an object onto the beginning of the in-memory list.<br /> *<br /> * @param object $newObject<br /> * @return int 1 for success, 0 for failure<br /> */<br /> public function prepend ($newObject)<br /> {<br /> return array_unshift($this->items, $newObject);<br /> }<br /><br /> /**<br /> * Insert an object into the list after $prevObject<br /> *<br /> * @param object $newObject<br /> * @param object $prevObject<br /> */<br /> public function insertAfter ($newObject, $prevObject)<br /> {<br /> $oldFocus = $this->key();<br /> <br /> $this->focus($prevObject);<br /> $k = $this->key();<br /> <br /> $this->items[$k] = $prevObject;<br /> <br /> $this->key = $oldFocus;<br /> }<br /><br />}<br /></pre><br />Hope this is useful. Maybe I just miss Java a lot, but it really bugs me that more of this functionality isn't baked into PHP. Anyway, this class gets really interesting when you start extending it in a database-backed way:<br /><pre><br />class UserFriendList extends ObjectList<br />{<br /> /**<br /> * Objects in this list are friends of this user<br /> *<br /> * @var User<br /> */<br /> protected $owner;<br /> <br /> public function __construct( User $user )<br /> {<br /> $this->owner = $user;<br /> <br /> $friendIds = $db->query( "SELECT user_id FROM friends WHERE owner_id = $user->user_id");<br /> foreach( $friendIds as $friend )<br /> {<br /> $this->items[] = new User( $friend );<br /> }<br /> }<br /> <br /> public function prepend( $newObject )<br /> {<br /> if( $db->query( "INSERT INTO friends (user_id, owner_id) VALUES ( $newObject->user_id, {$this->owner->user_id})"))<br /> {<br /> parent::prepend( $newObject );<br /> }<br /> }<br />}<br /></pre><br />...and so on and so forth. (Obviously this code is a quick example, don't run database queries like this!)<br /><br />Good luck, and good coding!intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-76369046002079090632007-11-03T20:43:00.000-07:002007-11-12T22:43:25.873-08:00Cheap event handlingWebsites, whether anyone realizes it or not, are basically event-driven. Request comes in, page comes out. Blogs go in, RSS goes out. Nothing changes until someone clicks the link, uploads the blog post, submits the form.<br /><br />Because pages are the only thing a user consciously requests, websites were traditionally organized on a page-by-page basis. The site was nothing without the pages. But pages make all kinds of decisions about what is going to occur on them. What forms are available, what information is displayed, the groupings of necessary data.<br /><br />In a large-scale system like Treemo, there is much more than pages to worry about. In fact, presenting the actual web pages represents maybe 30% of the logic in the system. <span style="font-style: italic;">Deciding what to do</span> is the real problem. Is this a request for an HTML page? An XHTML page? An SMS message? An RSS feed? A picture? An account upgrade? Once we've decided on what's being asked for, objects get inflated from the database and <span style="font-style: italic;">something happens.</span> Then what?<br /><br />Well, all kinds of things. Things happening in the system have side-effects. A user buying a paid account has their quota lifted. A photo being uploaded causes notifications to be sent. A site message being sent causes another user's inbox to have a new message.<br /><br />The list is very long. Any request- or event-driven system is basically built on these foundations. Something Has Happened, and Important Classes Need To Know. OO GUI frameworks took this to its logical conclusion. Dozens of event classes from AncestorEvent to UndoableEditEvent. Then classes can attach themselves to controls as listeners of these events, and be guaranteed that anytime the event occurs, the class will be notified.<br /><br />On the web, for some reason, I don't see this paradigm much. One trouble spot for us as a PHP shop is that we're guaranteed that class state is NOT maintained across requests. This is different from a native GUI application, in which all the classes are sitting in memory, so when the event fires, the listening object is already there. Also, the list of listeners is easily maintained as an in-memory data structure. Not so much in PHP. In PHP you have to back your objects with databases, and listener structures are not so simple to generically store.<br /><br />Of course, if you were really clever, you would use an ORM tool like DB_DataObject, but we aren't quite so fortunate (also DB_DataObject is really slow, and <a href="http://www.akbkhome.com/wiki.php/DBDO/index.html">DBDO</a> isn't ready yet). With or without ORM, you still have to have tables, how would that look?<br /><br />The first time we encountered this problem we had, as usual, far too little time to think about it, and the primary concern we had was that it be asynchronous. At the time, this need was driven by the idea that we needed to perform regular billing events for paid users. Subscription billing is generated by time elapsing, so there needed to be a way to schedule future events.<br /><br />Unfortunately, we designed a system that was Just Good Enough, and it has lasted all this time. It's fully asynchronous, which creates big problems in a dynamic system. State changes are very rapid, and being sure you placed EVERY piece of data EXACTLY in right the queue is too much detail to think about, and fails silently. Here's how it looks now:<br /><pre><br />$extra = array(<br /> 'message' => $_POST['message'],<br /> 'from' => $request->viewer->user_id<br />);<br />$evt = new event('share', $extra);<br />$evt->fire();<br /></pre><br />The fire() method inserts a row in the database containing the type, the extra array, and the timing data (when to process, etc.) A daemon then consumes the rows, and initiates handlers, which look something like this:<br /><pre><br />class shareHandler extends eventHandler<br />{<br /> /**<br /> * Handle a share event.<br /> *<br /> * extraData array:<br /> * 'to' => Array of Email address or phone numbers. Required.<br /> * 'from' => The user_id of the sender. Required.<br /> * 'what' => String, url of the page that is being shared.<br /> * 'message' => Text of the message. Defaults to _____.tpl<br /> */<br />public function handle() {<br />...<br />}<br /></pre><br />So this works fairly well, but the linchpin is that twiddly extraData bit. What happens when the caller fails to place the right data in there? Well, nobody will know it until the event daemon actually picks it off the queue, but that happens minutes or even hours after the request which generated the event occurred. <br /><br />What we'd like to happen is for the caller to know a lot more about what's going on. If the user sharing content types in an invalid phone number, for example, it would be best if s/he could be notified of it immediately. So one simple way to do that for us is to move the event processing right into the request processing and do away with the daemon.<br /><br />Much like MDB2, the event processor could be represented as a singleton object that all the classes could send events to - which it technically is now anyway. So, you want to share something? Try it this way:<br /><pre><br />$evt = new ShareEvent( $_POST['message'], $request->viewer->user_id );<br />$evtProc = EventProcessor::singleton();<br />$result = $evtProc->fire( $evt );<br /></pre><br />This fixes two of the major problems with the existing events. 1) Event parameters can be type-hinted and required in the constructor, because in this scheme <em>events</em> are typed, rather than <em>handlers</em>. 2) Notice how you get a $result back from the event firing? The request processor can actually deal with the results of the event's handling! The event processing itself can even throw exceptions if it needs to! This is much better.<br /><br />Of course, you might not even need the singleton object, depending on your model. We're going to need it for tracking purposes, but at its core this pattern is no different than a good object model for system events. To do away with the singleton, just have your Event interface expose the fire() method. Or even if you need central points of logic for event processing, have the abstract event() class implement fire() and call parent::fire() in your extenders. This has lots of room for flexibility.<br /><br />For more on events, and the observer pattern specifically, see the page on <a href="http://devzone.zend.com/article/5-PHP-Patterns-The-Observer-Pattern">The Observer Pattern</a> at ZDZ.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-84005529155647502812007-10-25T21:35:00.000-07:002007-10-25T22:03:37.493-07:00How to lose a million and be smarter for it<span style="font-style: italic;"><a href="http://www.amazon.com/What-Learned-Losing-Million-Dollars/dp/0963579495/ref=pd_bbs_sr_1/002-4962177-8841643?ie=UTF8&s=books&qid=1193373355&sr=8-1">What I Learned Losing a Million Dollars</a> </span>should have more recognition (not to mention distribution) because what it has to say is excellent. It tells the story of Jim Paul, a trader "from hunger" who rode some combination of luck, timing and hubris all the way to the Executive Board of the Chicago Mercantile Exchange. Then he gets a wild hare that bean oil futures are going to skyrocket, and when they do, he doesn't get out. He stays convinced that they're going to the moon... and they don't. $1,200,000 later, he loses his seat on the Exchange and discovers that he had never been a trader after all. Really just lucky.<br /><br />But why did he stay in? He proceeds to take the psychology of market loss on a trip around that mountain. After a market loss, most people study a different way to find profit in the market, in order to make the money back. And Paul did that for a while, but discovered that all the great traders contradicted themselves on what worked. Where they agreed was that one should not lose money. "There are as many ways to make money in the markets," says Paul, "as there are participants. But only two ways to lose it."<br /><br />Losses, Paul explains, occur either due to a failure of analysis or failure to execute on the analysis. Most analytical methods allow for some loss, which is expected, but catastrophic loss is typically due to a failure of execution. So, how do you not fail to execute?<br /><br />First, you must understand your motive for participating in the market. Is it a "<span style="font-style: italic;">profit motive</span>" or a "<span style="font-style: italic;">prophet motiv</span>e"? Do you want to be right or be rich? Because if you want to be right, a continuous process like business markets is probably not for you. You should play poker. If you want to be rich, you should be careful not to confuse the two, and proceed to make some further distinctions: what is your stop-loss point, why are you entering, and what is your goal, <span style="font-style: italic;">in that order.</span><br /><br />Applying this metric allows you to take the beneficial properties of games, that of discrete bets or gambles, and apply them to markets. You're "chunking" your risk exposure into discrete blocks that you are aware of <span style="font-style: italic;">before </span>you enter the market. Then your market play becomes a simple if-then series.<br /><br />I mention all this because I'm watching my father struggle after 8 months floundering in the job market, and his words echo that of Paul losing his bean-oil futures. "It'll turn around! I'll certainly have a job by April." Well, what if you don't? What's your stop-loss point? When are you going to exit this obviously failing market? Hiring, like any market, is a continuous process, and to forever expose yourself to the risk of your share in it plummeting is a fool's game.<br /><br />The lesson applies equally well to businesses. We've changed our profit strategy several times here at Treemo because we're not so foolish as to think being "right" will create a sustainable company. Mobile media sharing has a lot of possibility, and sticking to one of them in the face of obvious failure is a great way to burn a lot of investor money. We're happy with the stance we're taking right now, and it's getting a great response in the market. It took plenty of experimentation to get there.<br /><br />But more than that, we've got controls. The business plans have direction changes in them for when business dries up in any area. There's plenty of stop-loss, so I sleep well at night knowing that long before the bank account gets thin, we've got a plan. And so should you. What are the markets you invest in?<br /><br />Most of us have a job market in which our investment is our time and training. We also have networking markets that we play, knowledge markets, even habits. What are the areas that you've internalized external markets that fail you? Do you, like my father, see a dry job market as a reflection of your self-worth? Is that going to cause you to hold to a failing strategy just to prove yourself right? Or will you have the prescience to exit because you'd rather be rich?<br /><br /><br /><span style="font-style: italic;"></span>intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0tag:blogger.com,1999:blog-2793576348171340857.post-35754497723676078592007-10-20T21:12:00.000-07:002007-10-20T21:39:18.302-07:00How to get character encoding right in Smarty<a href="http://smarty.php.net/">Smarty </a>is a great tool, but like its underlying PHP, has no native understanding of internationalization. One aspect of it that's easy to get wrong is character output encoding. Fortunately it's also fairly easy to get right.<br /><br />HTTP useragents send a header with their requests named "<a href="http://www.freesoft.org/CIE/RFC/2068/157.htm">Accept-Charset</a>". This header has a really stupid format, the parsing of which I'll address another time. For now, let's start from the point where you know what encoding the client prefers. Let's say it's something really weird like BIG-5. All your Chinese Smarty templates have text stored in UTF-8 because you're a good programmer. How do you get those lovely UTF-8 templates mangled into something this silly UA likes?<br /><br />First, if you're even reading this post, you're probably aware of PHP's mbstring library. This library offers a lovely function called mb_convert_encoding. The basic idea is to attach an output filter to Smarty that runs the template output through mb_convert_encoding. Here's how this looks:<br /><br /><span style="font-family:courier new;">/**</span><br /><span style="font-family:courier new;"> * This sets an internal property in the PHP instance. Of course, this should be set to whatever the UA wants, within reason.<br /></span><span style="font-family:courier new;">*/</span><br /><br /><span style="font-family:courier new;">mb_http_output( "BIG-5" );<br /></span><br /><span style="font-family:courier new;">function convertEncoding( $templateOutput, &$smarty )<br />{<br />return mb_convert_encoding( $templateOutput, mb_http_output() );<br />}</span><span style="font-family:courier new;"></span><br /><br /><span style="font-family:courier new;">$smarty->register_outputfilter( "convertEncoding");<br /><br />/**<br /> * You must send headers so the browser knows that you gave it what it wanted<br /> */<br />header( "Content-Type: text/html; charset=".mb_http_output());</span><br /><br />Now, when you call display() or fetch() on that Smarty instance, all its <span style="font-style: italic;">output</span> will be converted to the current value of mb_http_output() - even if the file was already cached!<br /><br />This approach guarantees a few nice things:<br /><ul><li>It does not depend on output buffering</li><li>It is insulated from changes in php.ini</li><li>Only templates have their encoding converted</li><li>The encoding can be converted on a per-request basis</li></ul>So go get this right, and stop sending everybody ISO-8859-1.intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com2tag:blogger.com,1999:blog-2793576348171340857.post-19967415068954862072007-10-07T17:07:00.000-07:002007-10-20T21:10:58.058-07:00On ensuring conceptual integrity in systems design<a href="http://en.wikipedia.org/wiki/Fred_Brooks">Fred Brooks</a> outlines 3 essential points about systems design:<br /><ul><br /><li> Conceptual integrity is <i>the</i> most important consideration<br /><li> Deciding precisely what to build is the hardest part </li><br /><li> The focus of any organizational structure must be on solving the critical need for communication </li><br /></ul><br /><br />Some of these depend on each other, which can be represented graphically:<br /><img src="http://img442.imageshack.us/img442/2409/greatteamsba6.png" width=731 height=342 title="" ><br /><br />Of course, you can't just do these things in order. Over the course of everyone actually building the thing, decisions about what it will do must be revisited.<br /><img src="http://img208.imageshack.us/img208/5126/requirementcycledn1.png" width=437 height=334 title="" ><br /><br />So, to make a great system, first one must decide what it will do, then ensure that its concepts remain coherent, then communicate constantly and easily with everyone building it. At Treemo, I sit right at the second step in that list, and am working on becoming better at the third, as it is increasingly required of me.<br /><br />Anyway, these concepts are software engineering 101. Every tool, process, idea, program, or proposal that crosses my desk is going to have this diagram applied to it. "Does this thing solve a communication problem and/or reinforce our software's conceptual integrity?" It seems simple, but most things don't pass. Sometimes I feel like people turn their brains off when evaluating things like this. <a href="http://www.gamearchitect.net/Articles/SoftwareIsHard.html">Software is hard</a>, yes, but process of making it is over 50 years old, and the thousands of software projects that have lived and died in that time can tell us a LOT about how to make solving that hard problem easier. We have such ignorance of our history in this industry. I've only been a professional developer for a year now, and I've reinvented hundreds of wheels both in my code and in my organization. It's exhausting sometimes. <br /><br />Also worth addressing are the blocks on the bottom of the first diagram: transparent trust, shared vision, and sharp tools. <br /><br />First, you're not going anywhere in any company if you can't trust those around you. The need in software for fluent, open communication cannot be met without grab-my-hand-over-a-cliff trust. <a href="http://joelonsoftware.com/">Joel</a> is <a href="http://joelonsoftware.com/articles/FieldGuidetoDevelopers.html">right about this</a>, even though he doesn't come out and say it. Gifting your developers with offices, decision-making authority, and free lunches is a wonderful way to create a safe environment where they feel free to create and speak. <br /><br />Second, everyone making software must grasp their role in both the company and the development process. They must share that vision for their role and be enthusiastic about it, because if they're not, why are they working there? Being on-message is just as important for testers as it is salesmen.<br /><br />Finally, sharp tools must be available to everyone who needs them. Michelangelo was known to work so quickly and so hard that he would work all night with a candle on his head, chiseling furiously until the marble dust choked the air of his workshop. How many chisels do you think he owned? When you look at the <i>David</i>, do you wonder at how much he spent on having them sharpened? This also ties in with the trust issue: put a fast computer in that office. <br /><br />Now, to build a company wise enough that I don't have to spend Sundays thinking about this stuff...intjonathanhttp://www.blogger.com/profile/08349837127962896589noreply@blogger.com0