<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1127830764822879554</id><updated>2026-09-20T06:40:57.500-05:00</updated><category term="php"/><category term="programming"/><category term="coding"/><category term="development"/><category term="phpunit"/><category term="subversion"/><category term="testing"/><category term="conference"/><category term="code review"/><category term="ant"/><category term="google"/><category term="javascript"/><category term="review board"/><category term="style"/><category term="DIY"/><category term="beer"/><category term="buzz"/><category term="cryptography"/><category term="css"/><category term="curl"/><category term="framework"/><category term="keezer"/><category term="kegerator"/><category term="keybase"/><category term="security"/><category term="speaking"/><category term="static cling"/><category term="websvn"/><title type='text'>Digital Darkness</title><subtitle type='html'>Musings on PHP and software development.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>27</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-6342340544779802592</id><published>2015-02-23T15:54:00.000-06:00</published><updated>2015-02-23T15:54:30.962-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="coding"/><category scheme="http://www.blogger.com/atom/ns#" term="phpunit"/><category scheme="http://www.blogger.com/atom/ns#" term="testing"/><title type='text'>Unit testing headers in PHP</title><content type='html'>There&#39;s plenty of questions about unit testing code that sets headers using PHPUnit. PHPUnit officially assumes that neither the test code nor the tested code emit output or send headers:&lt;br /&gt;
&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJeG1RHiiYqNK6kCc0rQliZMIsVhMuH0XCn1CGj1ZAVzKmPqUwxCab1cpYy71Bqx3DMjStuXPWLeQIr9RfAS7-Ba-xxG6PitXhQVWDjCEysIt0pX4qsDvC-Ss8pVtJpK19qBghI8M_e807/s1600/Screen+Shot+2015-02-23+at+14.53.10+.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJeG1RHiiYqNK6kCc0rQliZMIsVhMuH0XCn1CGj1ZAVzKmPqUwxCab1cpYy71Bqx3DMjStuXPWLeQIr9RfAS7-Ba-xxG6PitXhQVWDjCEysIt0pX4qsDvC-Ss8pVtJpK19qBghI8M_e807/s1600/Screen+Shot+2015-02-23+at+14.53.10+.png&quot; height=&quot;43&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: xx-small; text-align: start;&quot;&gt;(from https://github.com/sebastianbergmann/phpunit/issues/720#issuecomment-10399612)&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
And rightfully so. Headers are essentially global state, which is hard to unit test. However, if you&#39;re willing to install the PECL extension Runkit, you can actually unit test headers if you really want to.&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/omnicolor/91805e5d718a608e5bad.js&quot;&gt;&lt;/script&gt;

I use the setupBeforeClass hook to only run the code once before the entire class runs. If you only have one method that you&#39;re testing it can all get stuck in the single test. I check if the runkit extension is loaded to avoid breaking the unit test suite if someone tries to run it that doesn&#39;t have runkit installed. The test that actually looks at the headers is marked with a requires annotation, which will mark that test as skipped if runkit isn&#39;t loaded. If every test in your test class tests headers, you can move that up to the class level if you&#39;d like. My particular class had some tests that didn&#39;t need runkit and I wanted those to still run for people that didn&#39;t have runkit loaded.</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/6342340544779802592/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2015/02/unit-testing-headers-in-php.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/6342340544779802592'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/6342340544779802592'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2015/02/unit-testing-headers-in-php.html' title='Unit testing headers in PHP'/><author><name>Omni Adams</name><uri>http://www.blogger.com/profile/01609332262756578629</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJeG1RHiiYqNK6kCc0rQliZMIsVhMuH0XCn1CGj1ZAVzKmPqUwxCab1cpYy71Bqx3DMjStuXPWLeQIr9RfAS7-Ba-xxG6PitXhQVWDjCEysIt0pX4qsDvC-Ss8pVtJpK19qBghI8M_e807/s72-c/Screen+Shot+2015-02-23+at+14.53.10+.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-3569486363452249816</id><published>2014-03-24T09:00:00.000-05:00</published><updated>2014-03-24T09:00:07.884-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="cryptography"/><category scheme="http://www.blogger.com/atom/ns#" term="keybase"/><category scheme="http://www.blogger.com/atom/ns#" term="security"/><title type='text'>Public key crypto</title><content type='html'>I recently received an invite for &lt;a href=&quot;http://keybase.io/&quot;&gt;keybase.io&lt;/a&gt;. For those of you that haven&#39;t looked into it, it boils down to a public repository of public keys attached to certain geeky identities (&lt;a href=&quot;http://github.com/&quot;&gt;Github&lt;/a&gt; and &lt;a href=&quot;http://twitter.com/&quot;&gt;Twitter&lt;/a&gt;, for now). It&#39;s a new way to build a web of trust.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Where keybase.io shines&lt;/h2&gt;
&lt;div&gt;
It is easy (relatively speaking) to install the tools and create your identity. It&#39;s easy (again, relatively speaking) to verify your identity on the two public sites that they&#39;ve picked for verification. The UI is much prettier than the open keyservers that we already had. For example,&amp;nbsp;&lt;a href=&quot;http://keyserver.ubuntu.com/&quot;&gt;keyserver.ubuntu.com&lt;/a&gt; is obviously written by geeks, for geeks as is &lt;a href=&quot;http://pgp.mit.edu/&quot;&gt;pgp.mit.edu&lt;/a&gt;.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
It&#39;s easy enough to invite your friends (if you have invites, that is) and publicly track them.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The most important piece that Keybase brings to the table is some minimal verification. With the public keyservers, you generate a key attached to an email address, then upload it to a keyserver. There&#39;s nothing that verifies that you actually own that address. With Keybase you&#39;ve got a key attached to an email address &lt;b&gt;and&lt;/b&gt;&amp;nbsp;to a Twitter account &lt;b&gt;and&lt;/b&gt;&amp;nbsp;a Github account.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Where keybase.io fails&lt;/h2&gt;
&lt;div&gt;
Unfortunately, this is a longer list.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
It is &lt;b&gt;relatively&lt;/b&gt;&amp;nbsp;easy to install. It is far from easy. It requires command line access and node installed, which is far from trivial for non-geeks.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
They don&#39;t really tell you what tracking &lt;b&gt;means&lt;/b&gt;. When you track someone, you&#39;re saying that you vouch for their identity. There are plenty of people that I &quot;know&quot; on Twitter&amp;nbsp;that I&#39;ve never met in real life. They may or may not be who I think they are.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
They encourage you to upload your private key to their server. You should &lt;b&gt;never&lt;/b&gt;&amp;nbsp;give your private key out to anyone, no matter how much you may trust them. They make a big deal about how they encrypt it and never see it, but you&#39;re expected to just trust them. They seem on the level, but for all that I know they&#39;re nothing more than a front for the NSA to get your private keys.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Once you&#39;ve connected with others and verified their identity, what then? For most people, cryptography is another step in the way of communicating with people. I doubt Keybase will convince many people to start encrypting their communications. Hopefully it will at least convince people to start signing their git commits. But without a plugin for Gmail and Outlook, I doubt many people will even be bothered to sign their emails, much less encrypt them.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
While it&#39;s easy enough to connect to people you know once you find them, it&#39;s not particularly easy to find them in the first place. This might be just because it is still in alpha. Hopefully they&#39;ll allow you to search your Twitter friends. Seems like an oversight since Twitter is one of the ways to verify your identity.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Overall thoughts&lt;/h2&gt;
&lt;div&gt;
I doubt Keybase will ever fully replace the existing public key infrastructure. It&#39;s pretty easy to verify identities there, but there&#39;s not much value-add over a prettier front end to the existing solutions. I hope there&#39;s a lot more to come as it leaves alpha.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
That being said I &lt;b&gt;really&lt;/b&gt;&amp;nbsp;hope that it takes off in a big way. Our email may be sent in the clear and stored in the clear. It was never really designed to avoid being read by third parties. Hopefully recent disclosures about the NSA make people realize that their communications are not safe. If just one more person protects their communications from spying, Keybase will have been a success in my eyes.&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/3569486363452249816/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2014/03/public-key-crypto.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/3569486363452249816'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/3569486363452249816'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2014/03/public-key-crypto.html' title='Public key crypto'/><author><name>Omni Adams</name><uri>http://www.blogger.com/profile/01609332262756578629</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total><georss:featurename>McKinney, TX, USA</georss:featurename><georss:point>33.1972465 -96.639782200000013</georss:point><georss:box>32.984697499999996 -96.962505700000008 33.4097955 -96.317058700000018</georss:box></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-2290597813397573627</id><published>2014-03-18T13:00:00.000-05:00</published><updated>2014-03-18T15:40:17.327-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="conference"/><title type='text'>Midwest PHP 2014 wrap up</title><content type='html'>&lt;a href=&quot;http://www.midwestphp.com/packages/mwphp/themes/midwest_php/images/logo_header.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img alt=&quot;Midwest PHP&quot; border=&quot;0&quot; src=&quot;http://www.midwestphp.com/packages/mwphp/themes/midwest_php/images/logo_header.jpg&quot; title=&quot;&quot; /&gt;&lt;/a&gt;Let&#39;s get the negative stuff out of the way first. It was cold. Like, really cold. And I&#39;m a wimp when it comes to the cold.&lt;br /&gt;
&lt;br /&gt;
Whew. Thanks for struggling through all of that. With that out of the way, let&#39;s talk about how awesome the conference was. This was the second year I was invited to speak, so I&#39;ll also do some comparing with &lt;a href=&quot;http://omni-spot.blogspot.com/2013/03/midwestphp.html&quot;&gt;2013&#39;s conference&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Location, location, location&lt;/h2&gt;
&lt;div&gt;
They moved into the city to a bigger college. It was a big improvement over last year&#39;s venue. The rooms were big enough for the audience and had plenty of power in the two smaller rooms. I only heard one speaker mention having A/V trouble: two sets of dead batteries for the microphone. I had a bit of trouble figuring out which building the conference was in, but once I did, it was easy to get from session to session.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The food was pretty good as well, with soft tacos the first day and sandwiches the second. They took good care of those with special dietary needs as well.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The after party&#39;s location was very intimate, and really helped to mix up the speakers and attendees, which &lt;a href=&quot;http://omni-spot.blogspot.com/2014/02/conference-speakers-come-out-of-your.html&quot;&gt;I feel very strongly about&lt;/a&gt;. I talked to every single person at the after party except for the people sitting down around the edges.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Content is king&lt;/h2&gt;
&lt;div&gt;
The worst talk that I saw was still very interesting to me. The selection of speakers was fantastic in both breadth of topics and depth of knowledge. The only knock I have on the list of speakers is that it&#39;s pretty much the same group of speakers you see at other conferences such as &lt;a href=&quot;https://www.skiphp.com/&quot;&gt;SkiPHP&lt;/a&gt;&amp;nbsp;and &lt;a href=&quot;http://lonestarphp.com/&quot;&gt;Lone Star PHP&lt;/a&gt;. The community really wants &lt;b&gt;you&lt;/b&gt;&amp;nbsp;(yes, you) to start speaking at your local user group and then at conferences. Trust me, you&#39;ve got some knowledge worth sharing and can do this.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
And for some reason, &lt;a href=&quot;https://joind.in/talk/view/10548&quot;&gt;I spoke as well&lt;/a&gt;. Despite my fear of public speaking and my nerves almost derailing my talk, it went over very well. The attendees were very welcoming and friendly.&lt;br /&gt;
&lt;br /&gt;
Conferences can be so humbling to see the amazing stuff that people are doing and willing to share with you. Many of the speakers literally wrote the book on their topic. You can see them talk about it, and then chat with them about it more afterwards. That&#39;s just amazing to me.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
The Atmosphere&lt;/h2&gt;
&lt;/div&gt;
&lt;div&gt;
Wow. So energy. Much connections.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The attendees were a friendly bunch, and the hallway track seemed to be on fire. The IRC back channel was very talkative, owing to some well-timed tweets to get new people on to Freenode to join the conversation that.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Overall Impression&lt;/h2&gt;
&lt;div&gt;
I really hope that certain non-MidwestPHP events don&#39;t conspire to keep me from being able to submit a talk to Midwest PHP 2015. I really like this conference, the people, and the experience and would love to come back next year.&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/2290597813397573627/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2014/03/midwest-php-2014-wrap-up.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/2290597813397573627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/2290597813397573627'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2014/03/midwest-php-2014-wrap-up.html' title='Midwest PHP 2014 wrap up'/><author><name>Omni Adams</name><uri>http://www.blogger.com/profile/01609332262756578629</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total><georss:featurename>University of St.Thomas – Minneapolis Campus, 1000 Lasalle Avenue, Minneapolis, MN 55403, USA</georss:featurename><georss:point>44.974625 -93.276811899999984</georss:point><georss:box>44.971817 -93.281854399999986 44.977433000000005 -93.271769399999982</georss:box></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-451915114563391565</id><published>2014-02-13T09:54:00.000-06:00</published><updated>2014-02-13T09:54:14.589-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="code review"/><category scheme="http://www.blogger.com/atom/ns#" term="development"/><category scheme="http://www.blogger.com/atom/ns#" term="style"/><title type='text'>Coding for easier and quicker code reviews</title><content type='html'>If your company doesn&#39;t do code reviews, you most certainly should. If you&#39;re bound and determined to ship crappy software by not doing reviews, this post won&#39;t do much for you.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Code is meant to be read&lt;/h2&gt;
&lt;div&gt;
Many people are under the mistaken impression that their code is meant for the computer&#39;s consumption. While it&#39;s true that a computer will consume your code, it is not who you&#39;re writing for. Programmers are your intended audience. That includes you. Coding a new project is the easy and short part of the software&#39;s lifecycle compared to the time code spends in the maintenance and upkeep phase.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
So, assuming that you want to write code that&#39;s easier to read as well as easier to review, here&#39;s a few tips. All of them come from dealing with real coworkers. If you&#39;ve worked with me and recognize something that you &lt;b&gt;still&lt;/b&gt;&amp;nbsp;do, you should feel bad. You&#39;re probably the reason I wrote this.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3&gt;
Descriptive variable names&lt;/h3&gt;
&lt;div&gt;
Yeah, that&#39;s just good software development practice, but it can really help during code reviews as well. A short variable name that doesn&#39;t describe what it does is much harder to figure out during a code review since most review tools (at least that I&#39;ve used) don&#39;t give a huge amount of context around the change. So while someone going in to edit the code might quickly figure out what the variable is supposed to contain, a reviewer has to dig.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Exceptions: Obviously loop control variables should be short ($i, $j, $k) as well as variables that use a single letter commonly, like representing coordinates ($x, $y, $z).&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3&gt;
Useless commit messages&lt;/h3&gt;
&lt;div&gt;
&quot;Fixed a bug&quot; or &quot;Made change suggested in review #1234&quot; are nearly useless. While they may be factually correct, they don&#39;t really tell the review what and why you&#39;re changing something. In the first case, the reviewer has to figure out what the bug was before they can decide that you&#39;ve fixed it (and fixed it in the best way). In the second case, they have to go back to the original review to figure out what the suggested change was (and whether it was a good idea in the first place) before they can determine whether you&#39;d fix the original problem.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Exceptions: If you&#39;ve got a code sniffer (PHPCS, for example) that complains about things, having a commit message of &quot;PHPCS&quot; is plenty clear that you&#39;re appeasing PHPCS.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3&gt;
Trailing commas&lt;/h3&gt;
&lt;div&gt;
This one is a bit controversial, especially if you frequently switch between PHP and javascript. PHP allows trailing commas for arrays while certain paste-eating browsers don&#39;t allow it in their javascript parsers. Creating an array with the trailing comma makes later reviews easy to read. For example, if you have an array like:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; $letters = array(&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;a&#39;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;b&#39;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;c&#39;&lt;br /&gt;&amp;nbsp; &amp;nbsp; );&lt;/span&gt;&lt;/blockquote&gt;
&lt;br /&gt;
and you need to add a new letter:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; $letters = array(&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;a&#39;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;b&#39;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;c&#39;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;d&#39;&lt;br /&gt;&amp;nbsp; &amp;nbsp; );&lt;/span&gt;&lt;/blockquote&gt;
&lt;br /&gt;
code review packages will show two changed lines:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; $letters = array(&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;a&#39;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;b&#39;,&lt;br /&gt;&lt;span style=&quot;background-color: lime;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;c&#39;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;d&#39;&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; );&lt;/span&gt;&lt;/blockquote&gt;
If you always include the trailing comma it makes it obvious during the review that you&#39;re only intending to add a new element. This is a trivial example, but it helps to show your change&#39;s &lt;b&gt;intent&lt;/b&gt;&amp;nbsp;better, which helps a reviewer better understand what you&#39;re doing.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Code reviews are meant to help you&lt;/h2&gt;
&lt;/div&gt;
&lt;div&gt;
Don&#39;t get your feelings hurt when someone points out mistakes that you&#39;ve made. Everyone is trying to get better. If you send shorter review requests that are easy to read, it&#39;s much more likely that you&#39;ll get responses quickly and with less confusion.&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/451915114563391565/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2014/02/coding-for-easier-and-quicker-code.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/451915114563391565'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/451915114563391565'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2014/02/coding-for-easier-and-quicker-code.html' title='Coding for easier and quicker code reviews'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total><georss:featurename>McKinney, TX, USA</georss:featurename><georss:point>33.1972465 -96.639782200000013</georss:point><georss:box>32.984697499999996 -96.962505700000008 33.4097955 -96.317058700000018</georss:box></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-4032640530608684056</id><published>2014-02-10T08:55:00.000-06:00</published><updated>2014-02-10T08:55:00.305-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="conference"/><category scheme="http://www.blogger.com/atom/ns#" term="speaking"/><title type='text'>Conference speakers, come out of your towers</title><content type='html'>I&#39;m not a great speaker. Heck, out of the hundred or so that I&#39;ve seen since I started speaking at conferences I&#39;m not sure I would even rate myself in the top 80. But I have noticed a few things that speakers could do to improve the conference experience for both attendees and speakers. After all, conferences are (well, they &lt;i&gt;should&lt;/i&gt; be) about the attendees, not the speakers.&lt;br /&gt;
&lt;br /&gt;
Most speakers do a pretty good job of interacting with attendees that come up and talk to the after their session, or in the hallway track later. But that&#39;s about all of the interaction the general public gets.&lt;br /&gt;
&lt;br /&gt;
That&#39;s not enough.&lt;br /&gt;
&lt;br /&gt;
Many speakers are friends. They may be friends that only hang out with each other at conferences, but they look forward to seeing each other. They may have not seen each other since the previous conference, so they understandably want to catch up. So they sit together at meals, they group together in other sessions, and disappear into the speakers&#39; lounge to talk privately.&lt;br /&gt;
&lt;br /&gt;
That robs attendees of the chance to get access to you. The speakers&#39; dinner, which most conferences have, is your chance to catch up. The rest of the time, speakers should work to make themselves available to attendees.&lt;br /&gt;
&lt;br /&gt;
I pick random tables at lunch to sit at. The conversations are almost always as interesting as the ones going on at the speakers&#39; tables (if not more so), but with new people.</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/4032640530608684056/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2014/02/conference-speakers-come-out-of-your.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/4032640530608684056'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/4032640530608684056'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2014/02/conference-speakers-come-out-of-your.html' title='Conference speakers, come out of your towers'/><author><name>Omni Adams</name><uri>http://www.blogger.com/profile/01609332262756578629</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total><georss:featurename>McKinney, TX, USA</georss:featurename><georss:point>33.1972465 -96.639782200000013</georss:point><georss:box>32.984697499999996 -96.962505700000008 33.4097955 -96.317058700000018</georss:box></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-6277107896696884792</id><published>2014-02-06T08:55:00.000-06:00</published><updated>2014-02-06T09:06:44.830-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="conference"/><title type='text'>SkiPHP Wrap Up</title><content type='html'>The last weekend was the first year for another regional &lt;a href=&quot;http://www.php.net/&quot;&gt;PHP&lt;/a&gt; conference called &lt;a href=&quot;http://www.skiphp.net/&quot;&gt;SkiPHP&lt;/a&gt;. Similar in many ways to other regional conference that I&#39;ve attended (&lt;a href=&quot;http://www.lonestarphp.com/&quot;&gt;Lone Star PHP&lt;/a&gt; and &lt;a href=&quot;http://www.midwestphp.com/&quot;&gt;Midwest PHP&lt;/a&gt;), SkiPHP was a fantastic opportunity for people local to the Salt Lake City area to attend a conference without having to miss (much) time from work for travel.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The speakers were typical for such an event, with a few new speakers sprinkled in among a subset of the typical PHP speakers. People from the PHP community looking to make the jump from speaking at user groups have a very good opportunity to speak at conferences such as these, and I encourage all of them to submit proposals. See what conferences have open CFPs at&amp;nbsp;&lt;a href=&quot;http://callingallpapers.com/&quot; rel=&quot;nofollow&quot;&gt;callingallpapers.com/&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
The Venue&lt;/h2&gt;
&lt;/div&gt;
&lt;div&gt;
The conference was held at a college. This came with both some good points and some bad points. First, the rooms were all equipped with AV equipment that worked just fine. I didn&#39;t hear about any problems with the equipment, with the lone exception of a static-prone wireless mic.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The three smaller rooms had plenty of seats and tables. They &lt;i&gt;were&lt;/i&gt; classrooms after all. Depending on the time of day, one of the rooms did have a problem with the sun causing glare on the screen, but the speaker and attendees were able to soldier through that without too much problem. They also didn&#39;t have much in the way of power outlets. My fancy power outlet came in very useful several times throughout the day.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The larger room was a lecture hall. I&#39;m really glad I didn&#39;t have to speak in that room. It was set up for a professor to stand behind a podium and lecture, which none of the speakers that I watched in that room had any interest in doing. All of them wanted to get out from behind that thing, which I applaud. There really wasn&#39;t anywhere else that speakers could put their laptops and still interact with them for speaker notes and advancing slides.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
The Talks&lt;/h2&gt;
&lt;div&gt;
All of the talks I saw were fantastic. I made it a point to see some speakers that I had never seen before, and checked out some talks that were on subjects that I was very unfamiliar with. The only exception was the keynote, which was an updated version of the talk that Laura gave at Lone Star PHP 2013.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Unfortunately, looking through the &lt;a href=&quot;http://joind.in/&quot;&gt;joind.in&lt;/a&gt;&amp;nbsp;pages for the talks, some of the speakers, especially the new ones, haven&#39;t claimed their talks, which makes me think that they&#39;re missing out on some of the feedback that people gave them. That&#39;s really important!&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
The Atmosphere&lt;/h2&gt;
&lt;div&gt;
Overall, it was a much more focused conference. Many of the conferences that I&#39;ve been to in the past were about making connections with other geeks and feeding off the combined energy of a room full of geeks to recharge your developer batteries first, and learning new things was cool too. Very few of the people that I talked to in the halls or at lunch seemed at all interested in connecting outside or after the conference. Perhaps I was just to brash for the average SkiPHP attendee.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Overall Impression&lt;/h2&gt;
&lt;div&gt;
SkiPHP was a very well-run conference that I would recommend to anyone from the area. If I can fit it into my company&#39;s new stricter conference attendance policies, I will definitely try to return next year and hopefully hit the slopes while I&#39;m there.&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/6277107896696884792/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2014/02/skiphp-wrap-up.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/6277107896696884792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/6277107896696884792'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2014/02/skiphp-wrap-up.html' title='SkiPHP Wrap Up'/><author><name>Omni Adams</name><uri>http://www.blogger.com/profile/01609332262756578629</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total><georss:featurename>Salt Lake City, UT, USA</georss:featurename><georss:point>40.7607793 -111.89104739999999</georss:point><georss:box>40.568390300000004 -112.21377089999999 40.9531683 -111.5683239</georss:box></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-4958516293743424648</id><published>2013-07-03T17:57:00.000-05:00</published><updated>2013-07-03T17:57:36.385-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="beer"/><category scheme="http://www.blogger.com/atom/ns#" term="DIY"/><category scheme="http://www.blogger.com/atom/ns#" term="keezer"/><category scheme="http://www.blogger.com/atom/ns#" term="kegerator"/><title type='text'>Keezer build</title><content type='html'>I like beer. I like brewing beer. But I don&#39;t like having to bottle my beer. I also like draft beer, but don&#39;t have any way of getting draft beer at home so that I never have to venture out into the outside world.&lt;br /&gt;
&lt;br /&gt;
I could just keg my beer one five gallon batch at a time, but my philosophy is that if it&#39;s worth doing it&#39;s worth overdoing. So I&#39;m building a five keg system.&lt;br /&gt;
&lt;br /&gt;
The freezer came from Home Depot. I normally don&#39;t do business with them because of their shady credit practices, but the deal was too good to pass up. The temperature controller was ordered from &lt;a href=&quot;http://amazon.com/&quot;&gt;Amazon.com&lt;/a&gt;. Most of the other hardware came from &lt;a href=&quot;http://www.midwestsupplies.com/&quot;&gt;Midwest Supplies&lt;/a&gt;, I spend way more money with them than I probably should. The CO2 tank and the Sanke tap were purchased from &lt;a href=&quot;http://www.kegsandbarrels.com/&quot;&gt;Kegs &amp;amp; Barrels&lt;/a&gt;. Paint and wood were purchased from &lt;a href=&quot;http://www.lowes.com/&quot;&gt;Lowes&lt;/a&gt;. My first commercial keg came from &lt;a href=&quot;http://www.choicebeverage.net/&quot;&gt;Choice Beverage&lt;/a&gt; in McKinney.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
&lt;b&gt;Parts list&lt;/b&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.homedepot.com/p/GE-7-0-cu-ft-Chest-Freezer-in-White-FCM7SUWW/203547578?keyword=FCM7SUWW#.Uchm1D772AY&quot; rel=&quot;nofollow&quot;&gt;Freezer&lt;/a&gt;&amp;nbsp;- $168&lt;/li&gt;
&lt;li&gt;5&amp;nbsp;&lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=5409&quot;&gt;5/16&quot; Gas Line Assembly - Ball Lock&lt;/a&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;-&amp;nbsp;$49.95&lt;/li&gt;
&lt;li&gt;5&amp;nbsp;&lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=5469&quot;&gt;Chrome Faucet with Brass Lever&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$79.95&lt;/li&gt;
&lt;li&gt;2 &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=8005&quot;&gt;5-Gallon Reconditioned Ball Lock Keg&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$119.98&lt;/li&gt;
&lt;li&gt;10 &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=8046&quot;&gt;Plastic Tubing Spacer&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$8.90&lt;/li&gt;
&lt;li&gt;5 &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=8019&quot;&gt;Ball Lock Liquid Disconnect, 1/4&quot; Barbed&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$29.95&lt;/li&gt;
&lt;li&gt;5 &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=5880&quot;&gt;Long Faucet Tap Handle&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$12.45&lt;/li&gt;
&lt;li&gt;5 &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=5900&quot;&gt;3&quot; Economy Shank w/o Nipple&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$84.95&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=25208&quot;&gt;Shank Securing Wrench&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$4.99&lt;/li&gt;
&lt;li&gt;5 &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=5104&quot;&gt;1/4&quot; Stainless Beer Tail Piece&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$12.50&lt;/li&gt;
&lt;li&gt;10 &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=8025&quot;&gt;O-Ring Set&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$29.90&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=5342&quot;&gt;5-Way CO2 Distributor, 5/16&quot;&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$75.99&lt;/li&gt;
&lt;li&gt;5 &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=5108&quot;&gt;Neoprene Washer&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$6.95&lt;/li&gt;
&lt;li&gt;50 ft &lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=5041&quot;&gt;3/16&quot; ID Beer Line&lt;/a&gt;&amp;nbsp;-&amp;nbsp;$37.50&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.midwestsupplies.com/searchspring/result/?cat=&amp;amp;q=8011&quot;&gt;Double CO2 Regulator&lt;/a&gt;&amp;nbsp;- $149.99&lt;/li&gt;
&lt;li&gt;Domestic Keg Coupler - $39.99&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/B00862G3TQ/ref=oh_details_o01_s01_i00?ie=UTF8&amp;amp;psc=1&quot;&gt;STC-1000 Temperature Controller&lt;/a&gt; - $21.99&lt;/li&gt;
&lt;li&gt;5lb CO2 tank - $109.99&lt;/li&gt;
&lt;li&gt;5 hex nuts - $7.45 (Homebrew Headquarters)&lt;/li&gt;
&lt;li&gt;2 &lt;a href=&quot;http://www.lowes.com/pd_71597-99999-1034147_4294934154_?productId=3196043&quot;&gt;3/4x12x72 stain grade panels&lt;/a&gt; (ie wood) - $31.94&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.lowes.com/pd_15350-23946-900050_0__?Ntt=15350&amp;amp;UserSearch=15350&amp;amp;productId=3014185&amp;amp;rpp=32&quot;&gt;3/4&quot; Poly Panel Insulation&lt;/a&gt; - $9.80&lt;/li&gt;
&lt;li&gt;2 &lt;a href=&quot;http://www.lowes.com/pd_127945-81-63628_0__?Ntt=127945&amp;amp;UserSearch=127945&amp;amp;productId=1010041&amp;amp;rpp=32&quot;&gt;Weatherstripping&lt;/a&gt; - $12.53&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.lowes.com/pd_179348-23749-10114_0__?Ntt=179348&amp;amp;UserSearch=179348&amp;amp;productId=3549108&amp;amp;rpp=32&quot;&gt;Extension cord&lt;/a&gt; - $8.47&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.lowes.com/pd_300702-70-PC100L_0__?Ntt=300702&amp;amp;UserSearch=300702&amp;amp;productId=3031564&amp;amp;rpp=32&quot;&gt;1&quot; hole saw&lt;/a&gt; - $5.88&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.lowes.com/pd_68131-37672-35024_0__?Ntt=68131&amp;amp;UserSearch=68131&amp;amp;productId=3037665&amp;amp;rpp=32&quot;&gt;Screws&lt;/a&gt; - $5.58&lt;/li&gt;
&lt;li&gt;8 &lt;a href=&quot;http://www.lowes.com/pd_140307-166-BL3_0__?Ntt=140307&amp;amp;UserSearch=140307&amp;amp;productId=3691734&amp;amp;rpp=32&quot;&gt;angle brackets&lt;/a&gt; - $20.56&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
The Build&lt;/h2&gt;
&lt;div&gt;
Here&#39;s the freezer before it gets completely repurposed:&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkCmSP4zLz4_HbN65aV5azwKgwTnHBPEr-RL6AfCfMfWcxpLHEsJPoae2j8ZaZ9U6wFky54N05x0sw6QQehZ9OJsANjjzbFXjg3zkD-61Lmbb7ClF9_Bf4WyvXgN0YYpai6V1NB38d3zwX/s1600/20130626_114944.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkCmSP4zLz4_HbN65aV5azwKgwTnHBPEr-RL6AfCfMfWcxpLHEsJPoae2j8ZaZ9U6wFky54N05x0sw6QQehZ9OJsANjjzbFXjg3zkD-61Lmbb7ClF9_Bf4WyvXgN0YYpai6V1NB38d3zwX/s320/20130626_114944.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
Cutting the wood with Jason&#39;s help:&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu7AsNA31xPifDj5gCdOxdQD3qSsxAyiIfFwwtNQF0fVrFeFP2XgptBzHL5sALWxM5-bVruTCHaK4WU3qwERIxSq7-QyjGwvAxQRjHF_eyGrgA7a5BJm18TaUz0FHQTi4Kku5MENicjNjx/s1600/20130701_110253.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu7AsNA31xPifDj5gCdOxdQD3qSsxAyiIfFwwtNQF0fVrFeFP2XgptBzHL5sALWxM5-bVruTCHaK4WU3qwERIxSq7-QyjGwvAxQRjHF_eyGrgA7a5BJm18TaUz0FHQTi4Kku5MENicjNjx/s320/20130701_110253.jpg&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXJr6Cj2MlXitiLboWXN6XPveYbuZzJmVuTP9mmet1Iq5gHR9uAKYgVPkBvLKxzfZixoh0pM7R8BAxztVqEnOMB4kWFJBF5pUh70UhvGh35lQAFaeVFHJqnuCTpVwj2AJLaVOFrrIQOjtW/s1600/20130701_110249.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXJr6Cj2MlXitiLboWXN6XPveYbuZzJmVuTP9mmet1Iq5gHR9uAKYgVPkBvLKxzfZixoh0pM7R8BAxztVqEnOMB4kWFJBF5pUh70UhvGh35lQAFaeVFHJqnuCTpVwj2AJLaVOFrrIQOjtW/s320/20130701_110249.jpg&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Attaching some 90 degree brackets and cutting the insulation:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKaKFD8JzWSb0w1bcpBjeDbouyNV47rf11GMktNHpBRn_4KRyeEJd_9X5tmE5fOVJdm6XkYodNbVz8aZ70DL5DkHfSeeDjDwlwOQYvnZLCgUIBLAlkJ-UMGU-C44a_U9AjuLQxU1HcEhx2/s1600/20130701_125202.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKaKFD8JzWSb0w1bcpBjeDbouyNV47rf11GMktNHpBRn_4KRyeEJd_9X5tmE5fOVJdm6XkYodNbVz8aZ70DL5DkHfSeeDjDwlwOQYvnZLCgUIBLAlkJ-UMGU-C44a_U9AjuLQxU1HcEhx2/s320/20130701_125202.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Drilling the one inch holes for the shanks:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCNu6RsoVyAk5NcsbP_rql9VWSeSfzm_WDUhy-EdtqS_sUgq32G7jaYBGblkCi9gCBuwuExpScnVe6oKI1l00VRwnHSXkc1cp2zOwNECP92FvG2viTlTB3s173CMaG0NrWgmh0e-Nvm-B5/s1600/20130701_154409.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCNu6RsoVyAk5NcsbP_rql9VWSeSfzm_WDUhy-EdtqS_sUgq32G7jaYBGblkCi9gCBuwuExpScnVe6oKI1l00VRwnHSXkc1cp2zOwNECP92FvG2viTlTB3s173CMaG0NrWgmh0e-Nvm-B5/s320/20130701_154409.jpg&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Installing the shanks:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdkbTSBCT5k_IhIaiS7QAsQ6e50wVoQnDpi5BmRa4YxtygagnbVqJLIrKd0_xEZ1UwGhffLLUszh9OCTmQFrSY4sI7-2oU9i8OHlJOzER2D16rkQ0tqw8KdXOFaMMAOvke3dCHYa2ygEnC/s1600/20130701_155639.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdkbTSBCT5k_IhIaiS7QAsQ6e50wVoQnDpi5BmRa4YxtygagnbVqJLIrKd0_xEZ1UwGhffLLUszh9OCTmQFrSY4sI7-2oU9i8OHlJOzER2D16rkQ0tqw8KdXOFaMMAOvke3dCHYa2ygEnC/s320/20130701_155639.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi00OK3dfVufYZqa8MbrDiRdT9-X3p49-kBTRWk78HJuTNGPdk1F9e3tsXRpIZi3Fo8Yqg74n18oenwk7x5LLCLEbcn8FINP85Uissrbi7gXAOwqUYT5SrerRgwXeGJEkPj2TkZDpuUlWGy/s1600/20130701_155632.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi00OK3dfVufYZqa8MbrDiRdT9-X3p49-kBTRWk78HJuTNGPdk1F9e3tsXRpIZi3Fo8Yqg74n18oenwk7x5LLCLEbcn8FINP85Uissrbi7gXAOwqUYT5SrerRgwXeGJEkPj2TkZDpuUlWGy/s320/20130701_155632.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Attaching the back to the side:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTDefpHWZNT0pcuAbslAhwSRwGNoOce7qZQsZcGqpY81HTCMi8NA_OvR4HrULLh8u-aVzfk9Q5GyQd9rDbSylhPkMcVDYio0zgrENm5IfS5Wstyg7kgo8a2WRgkXEpoKdVPFUF43r0K9iY/s1600/20130701_163306.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTDefpHWZNT0pcuAbslAhwSRwGNoOce7qZQsZcGqpY81HTCMi8NA_OvR4HrULLh8u-aVzfk9Q5GyQd9rDbSylhPkMcVDYio0zgrENm5IfS5Wstyg7kgo8a2WRgkXEpoKdVPFUF43r0K9iY/s320/20130701_163306.jpg&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Altogether now:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgba6qyhJtlKmEYc911d9uU0zsiHBnmtY_qIC66lKkwnreZWedFwP2IGFrSY6FGJF3Zm04ZSRJOB7MmIqJnwRKElcYDRKpEqNf9RPi5RNuac-zvMLHkEKRPypDr2IYipcueiM2ogVW5BEUB/s1600/20130701_163948.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgba6qyhJtlKmEYc911d9uU0zsiHBnmtY_qIC66lKkwnreZWedFwP2IGFrSY6FGJF3Zm04ZSRJOB7MmIqJnwRKElcYDRKpEqNf9RPi5RNuac-zvMLHkEKRPypDr2IYipcueiM2ogVW5BEUB/s320/20130701_163948.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9oPz8U-TthExt-NmH3Zan_Fw62xxRI7GXst0qbJR49dlbxwVXdeousHEpF_e1x3yVgfgGgBJGcORPByp4YgwlN6IoLLKnBFIJhuPk57Wf3nDijcjJc4nc16KfaHKzG1YEiqfzIxJWI1Dc/s1600/20130701_163749.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9oPz8U-TthExt-NmH3Zan_Fw62xxRI7GXst0qbJR49dlbxwVXdeousHEpF_e1x3yVgfgGgBJGcORPByp4YgwlN6IoLLKnBFIJhuPk57Wf3nDijcjJc4nc16KfaHKzG1YEiqfzIxJWI1Dc/s320/20130701_163749.jpg&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Attaching the gas distributor:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoGdgHiAOhVIAvZeh3jDWiu-bYIOFq4I9CHSOIhZaWWGMxT-bQhcQZ-pluw4Itr7cY_ZpMd83pyBd6NQ_QrWF8uHS1PHI1huwqktI-tUlmRF0JXs6wcvB0eCDYQOxMWxSYJTl0EOdMEaz1/s1600/20130701_165143.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoGdgHiAOhVIAvZeh3jDWiu-bYIOFq4I9CHSOIhZaWWGMxT-bQhcQZ-pluw4Itr7cY_ZpMd83pyBd6NQ_QrWF8uHS1PHI1huwqktI-tUlmRF0JXs6wcvB0eCDYQOxMWxSYJTl0EOdMEaz1/s320/20130701_165143.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWySTdVh4xpLbSRlOLiF_h7ToEbugJptfRJy4W2epDR5gyLl1mXIGCYoBoVfGYSLlmyKq8qLcgI1wiClINtOclC_meom3bFR1HVILHAdasUmsAs_548m179kfnSTV8t6UQQ9pfVNzx2_uz/s1600/20130701_164042.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWySTdVh4xpLbSRlOLiF_h7ToEbugJptfRJy4W2epDR5gyLl1mXIGCYoBoVfGYSLlmyKq8qLcgI1wiClINtOclC_meom3bFR1HVILHAdasUmsAs_548m179kfnSTV8t6UQQ9pfVNzx2_uz/s320/20130701_164042.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Backside of the installed shanks:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSUNYMl_7di48iRzsCOaeBGT50pUqdJZsC0ycuNOsYcz9II9Y4rTA_xlxTQWQVhIH3R4RO4R6r2Dw348IjBGvE3kMMRhFikxH19UOPbakQiq82aHmZeoyXuo-PmEY5iVI9KsQw48DMGwjb/s1600/20130701_164455.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSUNYMl_7di48iRzsCOaeBGT50pUqdJZsC0ycuNOsYcz9II9Y4rTA_xlxTQWQVhIH3R4RO4R6r2Dw348IjBGvE3kMMRhFikxH19UOPbakQiq82aHmZeoyXuo-PmEY5iVI9KsQw48DMGwjb/s320/20130701_164455.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Faucets installed (Midwest backordered one of the shanks):&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5PX5FG_59d05N999m5D7u9y080e-y0hbSo8WZ65uitgNa8yFWvydPQI5OoqlENFaW6rSssr-6cjz30_RdjvEDFQOozqxro9Wy4gGjzWK3WSElNDUCHBnhOcemW-g8hJMPfu93SnVSWKCR/s1600/20130701_165421.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5PX5FG_59d05N999m5D7u9y080e-y0hbSo8WZ65uitgNa8yFWvydPQI5OoqlENFaW6rSssr-6cjz30_RdjvEDFQOozqxro9Wy4gGjzWK3WSElNDUCHBnhOcemW-g8hJMPfu93SnVSWKCR/s320/20130701_165421.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Wiring up the temperature controller:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0ZMDp62soHxKlVCaZP4OWa6QMCJJYQYCjYA-LpUY_Pm0uqTLhAPCivw70S2AfM3j7Y7_lQjl5S-yaUJDe_29qpMh3KB9ARCRA27tc9y4GxtmIXHz8wruLnct9T58TqutPcQRb9n3LZqVK/s1600/20130701_171245.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0ZMDp62soHxKlVCaZP4OWa6QMCJJYQYCjYA-LpUY_Pm0uqTLhAPCivw70S2AfM3j7Y7_lQjl5S-yaUJDe_29qpMh3KB9ARCRA27tc9y4GxtmIXHz8wruLnct9T58TqutPcQRb9n3LZqVK/s320/20130701_171245.jpg&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMlk31OJ3km-sWyUoBATLxxQZx8XNe_7HPWYFqFbNE4FA5J38lJvSORKCBvyO2tUgYbraonKeJH0QLgIdylFbPrnSSs8rXHYFu-ljMzdh8vRG44dO0XvFshvGDIT2TYsNwiWNWE6vrCq02/s1600/20130701_171241.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMlk31OJ3km-sWyUoBATLxxQZx8XNe_7HPWYFqFbNE4FA5J38lJvSORKCBvyO2tUgYbraonKeJH0QLgIdylFbPrnSSs8rXHYFu-ljMzdh8vRG44dO0XvFshvGDIT2TYsNwiWNWE6vrCq02/s320/20130701_171241.jpg&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Semi-finished product, I thought it was meant for me, but Scriptlet thinks it made a good bed. I had to take off the hinges. I&#39;ll add them back with another set at some point:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDnFB_KAJJ6MiotNFZw3uUCz8RD5RoiMOUZlnDoI_1bBqQIblpzCBhA2Hk47UuypPzH2yQjwYeVvUeiXK9aqvPzGPBDeVC5gLZSTfgNTugv1e1ZpLN_U5jn9s8w73IVImvIZmQWnFv38iV/s1600/20130701_215613.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDnFB_KAJJ6MiotNFZw3uUCz8RD5RoiMOUZlnDoI_1bBqQIblpzCBhA2Hk47UuypPzH2yQjwYeVvUeiXK9aqvPzGPBDeVC5gLZSTfgNTugv1e1ZpLN_U5jn9s8w73IVImvIZmQWnFv38iV/s320/20130701_215613.jpg&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKrgNpyyiW-4vHrizhRLF78FBdjyAJ7yG2IkMTXX4cfnZrml1RuD7FitKMRLYq8b60MebsTNCkSMfrXC-1OqTtB1gKHLbunjIqP1OKOxiRZua5qosNn2KXrDQWukzK9xzSQ6OPr7UMEEwU/s1600/20130701_171501.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKrgNpyyiW-4vHrizhRLF78FBdjyAJ7yG2IkMTXX4cfnZrml1RuD7FitKMRLYq8b60MebsTNCkSMfrXC-1OqTtB1gKHLbunjIqP1OKOxiRZua5qosNn2KXrDQWukzK9xzSQ6OPr7UMEEwU/s320/20130701_171501.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
Inside after adding kegs and stuff. That&#39;s a keg of &lt;a href=&quot;https://untappd.com/beer/396968&quot;&gt;Omni&#39;s Hardly Workin&#39; Ale&lt;/a&gt; and &lt;a href=&quot;https://untappd.com/beer/52332&quot;&gt;Lakewood&#39;s Temptress&lt;/a&gt;. There&#39;s also a fermenter with a batch of &lt;a href=&quot;https://untappd.com/beer/396970&quot;&gt;Omni&#39;s Employment Anchor Away&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhe_pGE1Q3FsTopEiEkLDF7n4cttIbuvYtbhQQEQwO4o9gSCxR4E29rzAnJ323Fu9spv1jAp9w9981ml3pduw458UnhOVeqwzw_YBWzf7t-Uorz2xIHFjN1LZrH8O70D8JRZuAf4P_M1hLW/s1600/20130703_171606.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhe_pGE1Q3FsTopEiEkLDF7n4cttIbuvYtbhQQEQwO4o9gSCxR4E29rzAnJ323Fu9spv1jAp9w9981ml3pduw458UnhOVeqwzw_YBWzf7t-Uorz2xIHFjN1LZrH8O70D8JRZuAf4P_M1hLW/s320/20130703_171606.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNL8rbp_DOjugo-S3cFFX2bsrdAsCmROysQUWMwUe0OpYFVO_3GuRYgGqfbFSXolU42gB6Ogmic7eTUIs1h-rdGxNaVAsyUqtxh2Xh7j2Eiv30cgADXUB_uNP-jd8tdFsDRHoHZpSTzn0Y/s1600/20130703_171610.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNL8rbp_DOjugo-S3cFFX2bsrdAsCmROysQUWMwUe0OpYFVO_3GuRYgGqfbFSXolU42gB6Ogmic7eTUIs1h-rdGxNaVAsyUqtxh2Xh7j2Eiv30cgADXUB_uNP-jd8tdFsDRHoHZpSTzn0Y/s320/20130703_171610.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Future improvements:&lt;/h2&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Resizing the collar. I didn&#39;t cut it to the right size, so it&#39;s too deep. Apparently I suck at math. I blame imperial units and the American education system.&lt;/li&gt;
&lt;li&gt;Painting the whole thing very dark brown to match my office&#39;s furniture.&lt;/li&gt;
&lt;li&gt;Installing the last shank when it arrives.&lt;/li&gt;
&lt;li&gt;Mounting the temperature controller and a thermometer so I can see what&#39;s going on in there better and control it.&lt;/li&gt;
&lt;li&gt;Installing a drip pan.&lt;/li&gt;
&lt;li&gt;Adding more gas line. I bought pre-built gas lines from Midwest, but had to take two of them apart. One goes from the CO2 tank to the distributor, and one goes to the commercial Sanke tap.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Installing hinges. The lid will be hinged as well as the collar.&lt;/li&gt;
&lt;li&gt;Tracking system to show the beers on tap as well as how much of each is available.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/4958516293743424648/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2013/07/keezer-build.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/4958516293743424648'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/4958516293743424648'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2013/07/keezer-build.html' title='Keezer build'/><author><name>Omni Adams</name><uri>http://www.blogger.com/profile/01609332262756578629</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkCmSP4zLz4_HbN65aV5azwKgwTnHBPEr-RL6AfCfMfWcxpLHEsJPoae2j8ZaZ9U6wFky54N05x0sw6QQehZ9OJsANjjzbFXjg3zkD-61Lmbb7ClF9_Bf4WyvXgN0YYpai6V1NB38d3zwX/s72-c/20130626_114944.jpg" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>McKinney, TX, USA</georss:featurename><georss:point>33.1972465 -96.639782200000013</georss:point><georss:box>32.984697499999996 -96.962505700000008 33.4097955 -96.317058700000018</georss:box></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-1244596299350137046</id><published>2013-03-06T08:00:00.000-06:00</published><updated>2014-03-18T10:16:47.962-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="conference"/><category scheme="http://www.blogger.com/atom/ns#" term="php"/><title type='text'>MidwestPHP Review</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtEJiKDKMdXCD87yWDjh0qrX5nm2rqUsRanqXoYWyX9Zuu30YgYhcBnuzXSWE8UPr8T8_yh9rrlFZu3P6RcJIOrEL1HssW7RPzmRQP-qGKwyfswcpvzxACNFyP8Q40pE8ATplQNNpMjYFV/s1600/rsz_midwestphpconference_customlogodesign_rl3.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img alt=&quot;MidwestPHP logo&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtEJiKDKMdXCD87yWDjh0qrX5nm2rqUsRanqXoYWyX9Zuu30YgYhcBnuzXSWE8UPr8T8_yh9rrlFZu3P6RcJIOrEL1HssW7RPzmRQP-qGKwyfswcpvzxACNFyP8Q40pE8ATplQNNpMjYFV/s1600/rsz_midwestphpconference_customlogodesign_rl3.jpg&quot; title=&quot;&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
It seems like smaller regional PHP conferences are popping up all over the place these days. While they don&#39;t have the budgets of the bigger conferences like ZendCon and |Tek, they do provide something those other conferences just can&#39;t: an intimate setting. The newest conference on the block is &lt;a href=&quot;https://twitter.com/midwestphp&quot;&gt;MidwestPHP&lt;/a&gt;. I&#39;ve never been to Minnesota, but submitted two talks to them anyway. Both somehow got accepted and I had a blast at the conference, even while speaking (I&#39;m terrified of public speaking, but it is getting easier). Here&#39;s some thoughts on the conference:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
The Good&lt;/h2&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;Organization - Many attendees that I talked to had no idea it was a first time conference run by first time organizers. Things went relatively smoothly. Kudos to&amp;nbsp;&lt;a href=&quot;https://twitter.com/mikegstowe&quot;&gt;Mike&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href=&quot;https://twitter.com/jsundquist&quot;&gt;Jonathan&lt;/a&gt;&amp;nbsp;for a great job.&lt;/li&gt;
&lt;li&gt;Speaker gifts - As a fan of craft brews, I appreciated a four pack from a local brewery.&lt;/li&gt;
&lt;li&gt;Newer speakers - Smaller conferences with four tracks have space to take a chance on some new speakers, injecting some new blood into the speaking circuit. The PHP community seems to have the same speakers at many of its conferences. Midwest did a good job of mixing &quot;big name&quot; speakers (like &lt;a href=&quot;http://twitter.com/grmpyprogrammer&quot;&gt;Chris Hartjes&lt;/a&gt;) with relative newcomers (like me) as well as some first timers. Giving new people experience is great for the future since the larger conferences might feel safer accepting a talk from someone that has spoken before.&lt;/li&gt;
&lt;li&gt;Attendees - It&#39;s great seeing so many people so eager to soak up knowledge. I made note of a few people at the beginning of the conference and mentally stalked them throughout. Unlike many conferences I&#39;ve been to, they weren&#39;t always with the same group. Being willing to talk to random people at a conference is a great way to amplify your benefit from going to a conference.&lt;/li&gt;
&lt;li&gt;The venue - Some intimate rooms as well as larger rooms, all with tables. Lots of wide hallways for the important between session discussions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;
&lt;/h2&gt;
&lt;h2&gt;
The Less Good&lt;/h2&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;Speaker gifts - While I appreciate good beer, some people don&#39;t drink. And based on the four 4-packs I ended up with, some speakers weren&#39;t interested in carrying them home.&lt;/li&gt;
&lt;li&gt;The venue - The rooms were very spread out and difficult to find at first even with the map. More signs directing people where to find where they&#39;re going would be very helpful. Also, as with most conference rooms, more power would be awesome.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;No organized social - There were a few attendees that I would have liked to socialize with after the conference finished for the day, but with no social event to go to they just went home. So it ended up being just a bunch of the speakers socializing with each other. While the other speakers are cool and all, I already knew several of them. I&#39;m not implying that the social needs to be a huge party, but just reserving a large room at a nearby restaurant would be cool.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
The Ugly&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The cold - I hate it. My thin Texan blood still hasn&#39;t warmed back up to my normal body temperature.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;The snow - Delayed some flights and froze me even more. I admit it, I&#39;m a wimp when it comes to the cold.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;
The Sessions&lt;/h2&gt;
&lt;div&gt;
As for the sessions, with four tracks I obviously missed more sessions than I attended. Some sessions were easy for me to pick, like the ones where I was speaking. Some were easy for me to decide not to go to, like Chris&#39;s testing talk which is awesome, but I&#39;ve seen it. There was a good mix of basic, intermediate, and advanced talks as well as a good mix of front-end and back-end talks. The talks I personally really enjoyed:&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://joind.in/8224&quot;&gt;Scaling PHP with HipHop&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/SaraMG&quot;&gt;Sara Golemon&lt;/a&gt;&amp;nbsp;- Since the first time I heard about the HipHop project I&#39;ve been fascinated by it, even though I never really had a reason to use it. Her talk was interesting from a historical and architectural perspective. Again, I&#39;m probably still not going to have an excuse to run HipHop in production, I do plan on playing around with it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://joind.in/talk/view/8228&quot;&gt;Caching and Tuning fun for high scalability&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/wimgtr&quot;&gt;Wim Godden&lt;/a&gt; - Great pointers for when and how to use caching, with some real world examples. Much of this would be immediately useful to sites trying to scale up.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://joind.in/8232&quot;&gt;Embrace Your Inner Designer&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/joshbroton&quot;&gt;Josh Broton&lt;/a&gt; - This was a stretch for me, since I&#39;m about as far from a designer as they get (I don&#39;t &quot;do&quot; pretty). But I actually feel like I gained something from it to make me a slightly less shitty designer. And Josh probably had the best delivery of any speaker that I saw. Just don&#39;t tell him I said that.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
Overall, great conference. I learned a lot, met some great new people, and had a great time. I will probably make the trip up next year as well, though I may need a heavier ski jacket to protect me from the sub-zero temperatures.&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/1244596299350137046/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2013/03/midwestphp.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/1244596299350137046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/1244596299350137046'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2013/03/midwestphp.html' title='MidwestPHP Review'/><author><name>Omni Adams</name><uri>http://www.blogger.com/profile/01609332262756578629</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtEJiKDKMdXCD87yWDjh0qrX5nm2rqUsRanqXoYWyX9Zuu30YgYhcBnuzXSWE8UPr8T8_yh9rrlFZu3P6RcJIOrEL1HssW7RPzmRQP-qGKwyfswcpvzxACNFyP8Q40pE8ATplQNNpMjYFV/s72-c/rsz_midwestphpconference_customlogodesign_rl3.jpg" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>McKinney, TX, USA</georss:featurename><georss:point>33.1972465 -96.639782200000013</georss:point><georss:box>32.9847 -96.962505700000008 33.409793 -96.317058700000018</georss:box></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-5987376090778163334</id><published>2012-03-12T09:00:00.000-05:00</published><updated>2012-03-13T07:04:37.488-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="coding"/><category scheme="http://www.blogger.com/atom/ns#" term="phpunit"/><category scheme="http://www.blogger.com/atom/ns#" term="static cling"/><title type='text'>Unit testing code that uses static methods</title><content type='html'>Static calls are &lt;a href=&quot;http://java.dzone.com/articles/static-methods-are-death-testa&quot;&gt;death to testability&lt;/a&gt;. There are ways of &lt;a href=&quot;http://sebastian-bergmann.de/archives/883-Stubbing-and-Mocking-Static-Methods.html&quot;&gt;mocking out static calls made inside a class&lt;/a&gt;, but if you have some code that makes static calls &lt;b&gt;outside&lt;/b&gt;&amp;nbsp;a class, it&#39;s much more difficult. The best way is to refactor the static class to an instance class so you can mock things out.
&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/2016711.js?file=static.php&quot;&gt;
&lt;/script&gt;
&lt;br /&gt;
But if you&#39;re working in a legacy code base with large and ugly static classes, refactoring them may be overly difficult or hard to justify from a business perspective. But you still want to test the new code that you&#39;re writing that uses those static classes. &lt;a href=&quot;http://jonaquino.blogspot.com/&quot;&gt;A coworker&lt;/a&gt; showed me one way to do it. It&#39;s still hacky and not as good as fixing the static class properly.&lt;br /&gt;
&lt;br /&gt;
Basically instead of refactoring the crufty static code, you do the refactoring in the class you&#39;re writing. Here&#39;s an example class that uses the static Calculator class from above:&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/2016757.js?file=static-user.php&quot;&gt;
&lt;/script&gt;
&lt;br /&gt;
You can&#39;t mock out the Calculator class in this case. But you can make changes to your Foo class that will let you control the output from the static class. You can add protected methods to your class that call the static methods, and then mock those methods for testing and test the &lt;b&gt;mock&lt;/b&gt;&amp;nbsp;of the class you&#39;re testing.&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/2016778.js?file=static-mockable.php&quot;&gt;
&lt;/script&gt;
&lt;br /&gt;
Obviously this example is very simplistic, but in a more complicated class you could have the static class throw exceptions or different output or whatever.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;Now you have two problems&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
This technique is far from perfect. If you were to change the behavior of the protected methods of your class, the unit tests would still pass, but the code would no longer work the same in production.</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/5987376090778163334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2012/03/unit-testing-code-that-uses-static.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/5987376090778163334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/5987376090778163334'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2012/03/unit-testing-code-that-uses-static.html' title='Unit testing code that uses static methods'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total><georss:featurename>McKinney, TX, USA</georss:featurename><georss:point>33.1972465 -96.6397822</georss:point><georss:box>33.0909505 -96.7977107 33.3035425 -96.4818537</georss:box></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-8412669714845681742</id><published>2012-02-06T09:00:00.000-06:00</published><updated>2012-02-06T09:01:16.655-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ant"/><category scheme="http://www.blogger.com/atom/ns#" term="development"/><category scheme="http://www.blogger.com/atom/ns#" term="google"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="testing"/><title type='text'>Unit testing Google Closure applications from the command line</title><content type='html'>I&#39;ve been playing around with building an application using &lt;a href=&quot;http://closure-library.googlecode.com/&quot;&gt;Google Closure&lt;/a&gt;. I tried searching for a good way to run the unit tests from the command line as part of an &lt;a href=&quot;http://ant.apache.org/&quot;&gt;Ant&lt;/a&gt; build, but either there wasn&#39;t anything out there to do what I wanted to do or I just couldn&#39;t find it.&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
As an aside, naming a language after a programming construct is really dumb. If you try searching for blog posts on Google Closure, you get a bunch of stuff about javascript and closures since they&#39;re such an important language concept. I think &lt;a href=&quot;http://www.prototypejs.org/&quot;&gt;Prototype&lt;/a&gt; might have been a more successful language if they hadn&#39;t named themselves after a language construct as well, though the framework itself has its issues.&lt;/blockquote&gt;
I&#39;m used to writing code using test driven development, which doesn&#39;t work particularly well writing some Javascript. DOM-related code in particular is difficult to write with TDD. But there are parts of Javascript applications that can and should be well unit tested.&amp;nbsp;When coding in &lt;a href=&quot;http://www.php.net/&quot;&gt;PHP&lt;/a&gt; or &lt;a href=&quot;http://www.python.org/&quot;&gt;Python&lt;/a&gt;, I normally have two terminal windows open for my &lt;a href=&quot;http://www.vim.org/&quot;&gt;Vim&lt;/a&gt; sessions and one for my build and source code management activities. I&#39;ll typically save the file I&#39;m working on and them immediately alt-tab to the build window and hit up then enter to run my unit test target. I &lt;b&gt;really&lt;/b&gt; wanted to do that with Javascript.&lt;br /&gt;
&lt;br /&gt;
At &lt;a href=&quot;http://www.ning.com/&quot;&gt;my job&lt;/a&gt; we use &lt;a href=&quot;http://jquery.com/&quot;&gt;jQuery&lt;/a&gt;. This comes with &lt;a href=&quot;http://docs.jquery.com/Qunit&quot;&gt;Qunit&lt;/a&gt; which is easy to run automagically with &lt;a href=&quot;http://www.phantomjs.org/&quot;&gt;PhantomJS&lt;/a&gt;. But Qunit didn&#39;t look like it would be easy to make work with Closure in that it wouldn&#39;t handle the dependencies for me. And I might as well use the unit test framework for the library that I&#39;m working with. I had a few goals. I didn&#39;t want my Javascript tests to fire up a browser. That kind of thing should be handled by &lt;a href=&quot;http://seleniumhq.org/&quot;&gt;Selenium&lt;/a&gt;. I wanted to be able to add new tests without having to manually add the test name to any file. I wanted the output to be at least somewhat pretty and clean, particularly if there were no failures. As a PHP developer I&#39;m used to the &lt;a href=&quot;https://github.com/sebastianbergmann/phpunit/&quot;&gt;PHPUnit&lt;/a&gt; output:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpq_ODPuPK3jsdJDSRwwsHJCSdifwbeUruHOAAjf3jUpWJjiy5F8zAb0srlYcBgsVXyzdht4Je2k4qETHuC6W9X58L8pHIjzHVAJ0J17AUZRwqVR3RRZC2h4vlsSpbZa9L5enWq0YXHN0C/s1600/unittest.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;211&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpq_ODPuPK3jsdJDSRwwsHJCSdifwbeUruHOAAjf3jUpWJjiy5F8zAb0srlYcBgsVXyzdht4Je2k4qETHuC6W9X58L8pHIjzHVAJ0J17AUZRwqVR3RRZC2h4vlsSpbZa9L5enWq0YXHN0C/s320/unittest.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
I decided to try to code up my&amp;nbsp;own test runner to make this work. Here&#39;s the first version:&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/1717620.js&quot;&gt;
&lt;/script&gt;
&lt;br /&gt;
First, the build target concatenates all of the test files. In my case, test files are in a directory called tests and each of them ends with Test.js.&lt;br /&gt;
&lt;br /&gt;
Next, it fires up the &lt;a href=&quot;http://code.google.com/closure/compiler/&quot;&gt;Closure Compiler&lt;/a&gt;. In this example, we&#39;ve got a Javascript file called foo.js. We compile that with the concatenated test file (tests-concat.js) and the test runner (we&#39;ll get to that soon). The compiler creates a file called tests.js. We run that with phantomjs. The build will look something like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-E79xcGDhIFLlIp0C40CViC2cuXfayiLaqPWfmHrUF1MfaU1BIE6dSi4Gya9c9ECeumzjlXvYhAxn6_Nc-9e6GQWCVg6rT_Gzk8OFuzlWr2XS9nJ_CxG5IldTjTa2lMWo-eAdsiJ-jBfc/s1600/unittest-js.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;98&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-E79xcGDhIFLlIp0C40CViC2cuXfayiLaqPWfmHrUF1MfaU1BIE6dSi4Gya9c9ECeumzjlXvYhAxn6_Nc-9e6GQWCVg6rT_Gzk8OFuzlWr2XS9nJ_CxG5IldTjTa2lMWo-eAdsiJ-jBfc/s320/unittest-js.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Most of the magic happens in testRunner.js:&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/1717639.js?file=testRunner.js&quot;&gt;
&lt;/script&gt;
&lt;br /&gt;
Basically how it works is to override some methods in &lt;a href=&quot;http://closure-library.googlecode.com/svn/docs/class_goog_testing_TestCase.html&quot;&gt;goog.testing.TestCase&lt;/a&gt; to capture the results. We don&#39;t particularly care about successes, so they&#39;re replaced by dots. Failures show as an F and any errors are explained in more detail after all tests finish.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjepnlSM_Xdw9uI80zXBl6kknRzkNw7yYKIbA1ZM-AYSFxJgvVF3w14cXuEzumI2G_lWtwqepg2SDl53345l2bycS_rOI9pYu3sPNl2BRRU3Qhd-w066usoF4FgpSe0hnpRDX-bsuuHdIfC/s1600/failure.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;221&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjepnlSM_Xdw9uI80zXBl6kknRzkNw7yYKIbA1ZM-AYSFxJgvVF3w14cXuEzumI2G_lWtwqepg2SDl53345l2bycS_rOI9pYu3sPNl2BRRU3Qhd-w066usoF4FgpSe0hnpRDX-bsuuHdIfC/s320/failure.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
I&#39;ve uploaded all of the example code to Github at&amp;nbsp;&lt;a href=&quot;https://github.com/omnicolor/Closure-CLI-test-runner&quot;&gt;https://github.com/omnicolor/Closure-CLI-test-runner&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/8412669714845681742/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2012/02/unit-testing-google-closure.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/8412669714845681742'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/8412669714845681742'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2012/02/unit-testing-google-closure.html' title='Unit testing Google Closure applications from the command line'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpq_ODPuPK3jsdJDSRwwsHJCSdifwbeUruHOAAjf3jUpWJjiy5F8zAb0srlYcBgsVXyzdht4Je2k4qETHuC6W9X58L8pHIjzHVAJ0J17AUZRwqVR3RRZC2h4vlsSpbZa9L5enWq0YXHN0C/s72-c/unittest.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-5116873909959110746</id><published>2011-12-19T09:00:00.000-06:00</published><updated>2011-12-19T09:40:03.803-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="phpunit"/><category scheme="http://www.blogger.com/atom/ns#" term="programming"/><category scheme="http://www.blogger.com/atom/ns#" term="testing"/><title type='text'>One assertion per test</title><content type='html'>&lt;br /&gt;
&lt;div&gt;
There&#39;s a right way and a wrong way to write unit tests. I&#39;ve seen lots of wrong ways (like unit tests with no assertions) and differing shades of rightness. One thing that isn&#39;t really &quot;wrong&quot; but is not as right is writing your test cases with multiple assertions per test case.&lt;br /&gt;
&lt;br /&gt;
Here&#39;s a simple example of a test with multiple assertions:&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: x-small;&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;
function testSomethingThatReturnsAnArray() {&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; $value = somethingThatReturnsAnArray();&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Foo&#39;, $value[0]);&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Bar&#39;, $value[1]);&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Mitz&#39;, $value[2]);&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
This could easily be rewritten to multiple assertions:&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function testSomethingThatReturnsAnArrayFirstElement() {&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $value = somethingThatReturnsAnArray();&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Foo&#39;, $value[0]);&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function testSomethingThatReturnsAnArraySecondElement() {&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $value = somethingThatReturnsAnArray();&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Bar&#39;, $value[1]);&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function testSomethingThatReturnsAnArrayThirdElement() {&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $value = somethingThatReturnsAnArray();&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Mitz&#39;, $value[2]);&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div&gt;
What does this gain? You can immediately see whether &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;somethingThatReturnsAnArray()&lt;/span&gt; is broken for all of the tests or just the first. With the first example, if &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;$value[0]&lt;/span&gt; is not &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&#39;Foo&#39;&lt;/span&gt;, the test will immediately fail and you won&#39;t even test whether the next two elements are correct or not.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;But the method I&#39;m testing is slow!&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
If somethingThatReturnsAnArray() is so slow that you don&#39;t want to run it three times, you&#39;ve got two options:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Make the method faster.&lt;/li&gt;
&lt;li&gt;Only run it once per test suite run.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Obviously making the method faster would benefit your code the most overall, but it isn&#39;t always possible. But you can always just run it once, either by saving its output as a member variable of the test suite or making the tests depend on each other.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Saving the output is easy:&lt;/div&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;$result = null;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;public function testSomethingSlowThatReturnsComplexResult() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;result = somethingThatReturnsComplexResult();&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Foo&#39;, $this-&amp;gt;result[0]);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;public function testSomethingSlowThatReturnsComplexResultSecond() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Bar&#39;, $this-&amp;gt;result[1]);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;/blockquote&gt;
&lt;div&gt;
But there is a problem. Tests shouldn&#39;t require a certain order unless it is explicitly set. If you move the second test method above the first it will start failing. You could have a helper method that only executes the function once:&lt;/div&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;$result = null;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;private function runSomethingSlow() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; if (!$this-&amp;gt;result) {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; $this-&amp;gt;result = somethingThatReturnsComplexResult();&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; return $this-&amp;gt;result;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;public function testSomethingSlowThatReturnsComplexResult() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $result = runSomethingSlow();&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Foo&#39;, $result[0]);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;public function testSomethingSlowThatReturnsComplexResultSecond() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $result = runSomethingSlow();&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(&#39;Bar&#39;, $result[1]);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;/blockquote&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Now the slow function only is executed a single time, and the test methods don&#39;t have an unnamed dependency on any other tests.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;But my method is really complicated!&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
If you have a very complicated method that has lots of dependencies, you may be tempted to test actions on all of the dependencies at the same time as well as the return value so you don&#39;t have to have all of the boilerplate code repeated. I&#39;ve heard some otherwise very talented coders make this argument, which always surprises me. Test code is not really different from production code in that if you find that you&#39;re repeating code several times, it should be factored out into a method.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Let&#39;s try testing everything about this method:&lt;/div&gt;
&lt;div&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function compareAppleToOrange(Apple $apple, Orange $orange) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; return array(&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;sizeDifference&#39; =&amp;gt; $apple-&amp;gt;getSize()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - $orange-&amp;gt;getSize(),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;peelDifference&#39; =&amp;gt; $apple-&amp;gt;getPeelWidth()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - $orange-&amp;gt;getPeelWidth(),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; );&lt;br /&gt;}&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div&gt;
You could test it all in one big test:&lt;/div&gt;
&lt;div&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function testCompareAppleToOrange() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple = $this-&amp;gt;getMock(&#39;Apple&#39;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; array(&#39;getSize&#39;, &#39;getPeelWidth&#39;));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getSize&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(4));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getPeelWidth&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(2));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange = $this-&amp;gt;getMock(&#39;Orange&#39;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; array(&#39;getSize&#39;, &#39;getPeelWidth&#39;));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getSize&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(3));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getPeelWidth&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(3));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $expected = array(&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;sizeDifference&#39; =&amp;gt; 1,&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &#39;peelDifference&#39; =&amp;gt; -1,&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; );&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $result = compareAppleToOrange($apple, $orange);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals($expected, $result);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;/blockquote&gt;
&lt;br /&gt;
Better yet, you can test each piece on its own:&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function testCompareAppleToOrangeSize() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple = $this-&amp;gt;getMock(&#39;Apple&#39;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; array(&#39;getSize&#39;, &#39;getPeelWidth&#39;));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getSize&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(4));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple-&amp;gt;expects($this-&amp;gt;any())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getPeelWidth&#39;);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange = $this-&amp;gt;getMock(&#39;Orange&#39;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; array(&#39;getSize&#39;, &#39;getPeelWidth&#39;));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getSize&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(3));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange-&amp;gt;expects($this-&amp;gt;any())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getPeelWidth&#39;);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $result = compareAppleToOrange($apple, $orange);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(1, $result[&#39;sizeDifference&#39;]);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function testCompareAppleToOrangePeel() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple = $this-&amp;gt;getMock(&#39;Apple&#39;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; array(&#39;getSize&#39;, &#39;getPeelWidth&#39;));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple-&amp;gt;expects($this-&amp;gt;any())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getSize&#39;);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getPeelWidth&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(2));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange = $this-&amp;gt;getMock(&#39;Orange&#39;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; array(&#39;getSize&#39;, &#39;getPeelWidth&#39;));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getSize&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(3));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange-&amp;gt;expects($this-&amp;gt;any())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getPeelWidth&#39;);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $result = compareAppleToOrange($apple, $orange);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(-1, $result[&#39;peelDifference&#39;]);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;/blockquote&gt;
This allows you to change the peel calculation without touching the test for size, or the size calculation without touching the peel calculation.&lt;br /&gt;
&lt;br /&gt;
If you wanted to really split the test up, you can go even further since the mocks &amp;nbsp;provide some assertions of their own with the expects() calls. We&#39;ll split up the sizeDifference tests:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;/**&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp;* @return array Array from apples to orange comparison.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp;*/&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function testCompareAppleToOrangeSizeCalls() {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple = $this-&amp;gt;getMock(&#39;Apple&#39;,&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; array(&#39;getSize&#39;, &#39;getPeelWidth&#39;));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getSize&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(4));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $apple-&amp;gt;expects($this-&amp;gt;any())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getPeelWidth&#39;);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange = $this-&amp;gt;getMock(&#39;Orange&#39;,&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; array(&#39;getSize&#39;, &#39;getPeelWidth&#39;));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange-&amp;gt;expects($this-&amp;gt;once())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getSize&#39;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;will($this-&amp;gt;returnValue(3));&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $orange-&amp;gt;expects($this-&amp;gt;any())&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;method(&#39;getPeelWidth&#39;);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; return compareAppleToOrange($apple, $orange);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;/**&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp;* @depends testCompareAppleToOrangeSizeCalls&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp;* @param array $result Result of comparison.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp;*/&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;function testCompareAppleToOrangeSize($result) {&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; $this-&amp;gt;assertEquals(1, $result[&#39;sizeDifference&#39;]);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace; font-size: x-small;&quot;&gt;}&lt;/span&gt;&lt;/blockquote&gt;
Each split gives you more information on a failure and makes debugging test failure easier as well as reducing the changes required for refactoring.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/5116873909959110746/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2011/12/one-assertion-per-test.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/5116873909959110746'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/5116873909959110746'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2011/12/one-assertion-per-test.html' title='One assertion per test'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-4656128761136825234</id><published>2011-12-12T09:00:00.000-06:00</published><updated>2012-03-15T12:30:35.699-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="coding"/><category scheme="http://www.blogger.com/atom/ns#" term="programming"/><title type='text'>What&#39;s in a (method) name?</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;http://s3.amazonaws.com/kym-assets/photos/images/original/000/000/122/bush_doing_it_wrong_1.jpg?1318992465&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://s3.amazonaws.com/kym-assets/photos/images/original/000/000/122/bush_doing_it_wrong_1.jpg?1318992465&quot; /&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Through my coding career I&#39;ve come with some signs that the method I&#39;m writing or reviewing probably needs to be refactored. These aren&#39;t hard and fast rules, but in general these are sniffs that something may be amiss.&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;&lt;b&gt;Methods with &#39;or&#39; or &#39;and&#39; in their names&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;It&#39;s a pretty good sign that you&#39;re doing too much in a single method if it has the word &#39;and&#39; in its name. For example, addMessageAndNotifyUser() should probably be split into two methods: addMessage() and notifyUser().&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Methods with &#39;or&#39; in their name aren&#39;t as easy to classify. If the whole method is just a single conditional, the method name may make perfect sense. I would argue that it is just poorly named.&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;&lt;b&gt;Methods with almost the same name&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;If you&#39;ve got multiple methods that all have basically the same name except for a useless word, you&#39;re probably making the code harder to understand. For example if you wanted to add a message, which of these would you call?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;addMessage&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;addMessageProper&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;addMessageActual&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;If the first addMessage calls one of the other ones, is adding a message its main task? Could it be renamed to something that makes more sense?&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;&lt;b&gt;Methods that start with &#39;do&#39;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Do is always a wasted word. I see this mostly from coders that got their start with Visual Basic. Just don&#39;t &#39;do&#39; it.&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;&lt;b&gt;Why does it matter?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif; font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Readability is the most important thing to strive for in code. Bug free, maintainable, and fast code come from code that is readable. Most of the code&#39;s life cycle is spent in maintenance. The longest part of a typical maintenance task is to figure out what a particular piece of code is trying to do. Good documentation goes a long way towards making code understandable, but if the methods are actually well named you won&#39;t even need to look at the documentation.&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;&lt;b&gt;Update (2012-03-15):&lt;/b&gt;&amp;nbsp;I felt the need to revisit this post. Poor naming in the code base I&#39;m working on wasted several hours of my day. I was trying to find where a set of objects was getting loaded from our data store. Looking through other code that did the same type of thing was a deep rabbit hole of included files and static calls to other classes. Finally I asked a teammate where the objects get loaded. He pointed out that they were loaded in a method named &#39;handleSortingAndPagination&#39;. The method name doesn&#39;t provide any hint that it loads the objects from the data store before sorting and paginating them. The documentation for the method didn&#39;t either. This adds another bad naming smell: &lt;b&gt;names that do not reflect what the method does&lt;/b&gt;.&lt;/span&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/4656128761136825234/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2011/12/whats-in-method-name.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/4656128761136825234'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/4656128761136825234'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2011/12/whats-in-method-name.html' title='What&#39;s in a (method) name?'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-2451917195612966190</id><published>2011-11-22T09:00:00.000-06:00</published><updated>2011-11-28T11:34:14.066-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="development"/><category scheme="http://www.blogger.com/atom/ns#" term="php"/><category scheme="http://www.blogger.com/atom/ns#" term="programming"/><category scheme="http://www.blogger.com/atom/ns#" term="style"/><title type='text'>Consistency is the key</title><content type='html'>&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;float: left; margin-right: 1em; text-align: left;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;http://farm3.staticflickr.com/2444/3549285383_11de3317a6_m_d.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://farm3.staticflickr.com/2444/3549285383_11de3317a6_m_d.jpg&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: xx-small;&quot;&gt;http://www.flickr.com/photos/richard-g/3549285383/&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
In my last post, &lt;a href=&quot;http://omni-spot.blogspot.com/2011/11/keeping-it-simple.html&quot;&gt;Keeping it simple&lt;/a&gt;, I wrote about a few things that can make you a better coder, or at least a more valuable member of a coding team. This is the next step down the path of coding nerdvana.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Style isn&#39;t just for the stylish&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
Every coder has their own preferred style. Left to our own devices, we tend to write code our own way. As long as you&#39;re the only one looking at the code, this isn&#39;t a problem, but consistency should still be valued.&lt;br /&gt;
&lt;br /&gt;
If you&#39;re part of a team, you hopefully have a well documented style guide that everyone follows. Hopefully it covers the gritty details so that developers don&#39;t get into fist fights with each other about style differences. The last thing that a growing code base needs is for you to be able to tell who wrote a piece of code without checking your source control&#39;s blame log.&lt;br /&gt;
&lt;br /&gt;
But what if it doesn&#39;t cover a style point?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;http://picture.funnycorner.net/funny-pictures/4865/consistency.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;167&quot; src=&quot;http://picture.funnycorner.net/funny-pictures/4865/consistency.jpg&quot; width=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Stay consistent!&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
Perhaps it&#39;s my upbringing as a military brat or my overly-logical thought process, but I just can&#39;t handle disorder in code. It seems so simple to me, but I see code like this entirely too often:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;function foo($bar, $mitz) {&lt;br /&gt;&amp;nbsp; &amp;nbsp; if ( 0 == $bar ) {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; doSomething();&lt;br /&gt;&amp;nbsp; &amp;nbsp; }&lt;br /&gt;&amp;nbsp; &amp;nbsp; if($mitz == 0){&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; doSomethingElse();&lt;br /&gt;&amp;nbsp; &amp;nbsp; }&lt;br /&gt;}&lt;/span&gt;&lt;/blockquote&gt;
There are some very valid reasons to write your if conditions one way or the other (0 == $bar instead of $bar == 0). I&#39;m sure there are people that will make arguments about whether to put spaces outside of the if condition parenthesis, or extra spaces inside them. But doing it two different ways in a single method is just crazy.&lt;br /&gt;
&lt;br /&gt;
Mixing styles in your code makes it an order of magnitude more difficult to read. Code that is hard to read is hard to maintain. Your code will spend more time in maintenance than in development, so why wouldn&#39;t you do everything you could to make it easier to maintain?</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/2451917195612966190/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2011/11/consistency-is-key.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/2451917195612966190'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/2451917195612966190'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2011/11/consistency-is-key.html' title='Consistency is the key'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-7082260734671078460</id><published>2011-11-21T09:00:00.000-06:00</published><updated>2011-11-21T09:00:00.954-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="coding"/><category scheme="http://www.blogger.com/atom/ns#" term="development"/><category scheme="http://www.blogger.com/atom/ns#" term="programming"/><title type='text'>Keeping it simple</title><content type='html'>&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;float: left; margin-right: 1em; text-align: left;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;http://farm1.static.flickr.com/38/102766969_11f9467480.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;150&quot; src=&quot;http://farm1.static.flickr.com/38/102766969_11f9467480.jpg&quot; width=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;http://www.flickr.com/photos/r_rose/102766969/&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: xx-small;&quot;&gt;http://www.flickr.com/photos/r_rose/102766969/&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
I make no claims at being really smart. I don&#39;t even claim to have above average intelligence. But I have worked at companies that have a higher than average bar for employment and consequently above average employees. And working with really smart people, I&#39;ve noticed that they tend to make one common mistake:&lt;br /&gt;
&lt;br /&gt;
They write really complicated code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;A lone wolf&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
If you&#39;re a single developer working on a project, complicated code &lt;b&gt;might&lt;/b&gt;&amp;nbsp;be okay for you to write. Assuming that you write good documentation &lt;b&gt;and&lt;/b&gt;&amp;nbsp;are as smart as you think you are, you can write some really clever code. If you are truly as smart as you think you are, you can then maintain that code when you come back to it later. My experience with my personal projects has led me to a few realizations:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;I&#39;m not as smart as I once thought I was&lt;/li&gt;
&lt;li&gt;I&#39;m not as good at writing documentation as I thought I was&lt;/li&gt;
&lt;li&gt;My unit tests aren&#39;t as clear as I thought they were&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Again, you may be smarter than me, but you&#39;re probably not as clever as you think you are.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Joining the pack&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
Now, join a team of developers. You&#39;ve got a group of people with varying skills and experiences. None of them are as smart or clever as they think they are. If you can&#39;t even understand the complicated code that you wrote as a team of one, how likely is it for the rest of your team to understand your code?&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
You&#39;re no longer writing code in a vacuum. It almost immediately becomes impossible for any team member to understand how the whole system works as the system becomes more complicated. So each developer has little fiefdoms that they wrote, and since they&#39;re trying to impress other developers they make sure their intelligence shows through in the code.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Now, join your team with other teams in the workplace... You see where I&#39;m going with this?&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Your sanity went that way&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
The solution is to keep it simple. Assume that when your code breaks, you&#39;re going to be expected to fix it on a Friday night after drinking a dozen beers or at the darkest part of the morning when you&#39;ve run out of coffee. The last thing you want is to have to figure out what your code is doing before you can fix the problem. That means write more documentation about &lt;b&gt;how&lt;/b&gt;&amp;nbsp;the code actually works and &lt;b&gt;what&lt;/b&gt;&amp;nbsp;it is actually doing. It means avoiding anything that makes it more complicated than it needs to be.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Things to avoid in your simple code:&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;Big methods - They&#39;re hard to write, hard to test, hard to debug, and most importantly hard to understand.&lt;/li&gt;
&lt;li&gt;Magic numbers - If you don&#39;t immediately know what a number means by looking at it, it should be replaced by a constant. And even if you know what the number means, does everyone on your team know? Many coders know that there are 86,400 seconds in a day, but that doesn&#39;t mean it shouldn&#39;t be replaced by a constant.&lt;/li&gt;
&lt;li&gt;Conditionals - Sure, you&#39;re going to need if statements to write a decent sized program, but each branch your method has increases its complexity. You can have a small method that is extremely hard to understand if there are many branches.&lt;/li&gt;
&lt;li&gt;Planning ahead - Programmers tend to be lazy. We try to think of every possibility ahead of time and program for things that may never happen. We needlessly complicate simple code thinking we can see into the future. And if that future never happens (more likely then we would like to admit) the code is wasted. And worse then wasted, it&#39;s difficult to understand. Since the code is only supposed to do one thing but you&#39;ve coded it to do three, maintainers will assume that the three things it does are all equally important.&lt;/li&gt;
&lt;li&gt;Bad names - As part of the growing complexity, it&#39;s easy to throw an extra bit of functionality into an unrelated method. Suddenly your simple method sendMessage(), which should just send a message from point A to point B can send a message or log you out of an application or change a configuration option. But if you&#39;re not intimately familiar with the code, you naively assume that the method just sends a message.&lt;/li&gt;
&lt;li&gt;Static functions - Static classes and methods look great. You can call them from anywhere, and you can consolidate the similar functionality into a class. You can even unit test the heck out of that static class. But they rapidly increase the complexity of your lower-level code. They easily allow you to include huge chunks of functionality all over your application just by making a static call. That sendMessage() function needs permission, so it&#39;s easy to add a SecurityHelper::hasPermission() call inside sendMessage(). Suddenly, sendMessage() doesn&#39;t just send a message. It really becomes sendMessageIfSecurityHelperHasPermissionSaysSo().&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;
Writing lots of documentation, adhering to a style guide, and doing test driven development can help keep your code simpler. You&#39;ll thank yourself later, trust me.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/7082260734671078460/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2011/11/keeping-it-simple.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/7082260734671078460'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/7082260734671078460'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2011/11/keeping-it-simple.html' title='Keeping it simple'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://farm1.static.flickr.com/38/102766969_11f9467480_t.jpg" height="72" width="72"/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-2758922004546233359</id><published>2011-11-10T11:06:00.005-06:00</published><updated>2011-11-10T11:06:49.557-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="code review"/><category scheme="http://www.blogger.com/atom/ns#" term="coding"/><category scheme="http://www.blogger.com/atom/ns#" term="development"/><category scheme="http://www.blogger.com/atom/ns#" term="php"/><category scheme="http://www.blogger.com/atom/ns#" term="phpunit"/><category scheme="http://www.blogger.com/atom/ns#" term="programming"/><category scheme="http://www.blogger.com/atom/ns#" term="review board"/><title type='text'>Building rock solid software in the real world</title><content type='html'>&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;float: left; margin-right: 1em; text-align: left;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;http://farm1.static.flickr.com/22/32995286_16f7f96b20_m.jpg&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://farm1.static.flickr.com/22/32995286_16f7f96b20_m.jpg&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;http://www.flickr.com/photos/preef/32995286/&quot; style=&quot;font-size: x-small; text-align: -webkit-auto;&quot;&gt;http://www.flickr.com/photos/preef/32995286/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Recently (2011-11-08) I gave a talk at the &lt;a href=&quot;http://www.meetup.com/dallasphp/&quot;&gt;Dallas PHP meetup&lt;/a&gt; about building rock solid software as a team. For my first experience talking in front of a crowd since high school, I thought it went pretty well. Several people have asked for me to post my slides (&lt;a href=&quot;http://www.slideshare.net/omnicolor/building-rock-solid-software-in-the-real-world&quot;&gt;which I did&lt;/a&gt;), but they were made in a way that doesn&#39;t really help people out if they didn&#39;t see the talk. The talk was recorded and is available on &lt;a href=&quot;http://www.ustream.tv/recorded/18398170&quot;&gt;Ustream&lt;/a&gt;, but I thought it might be helpful to do a blog post on the topics I covered as well.&lt;br /&gt;
&lt;br /&gt;
This post is mainly meant to aggregate links to the topics that I talked about.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Tools&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
I covered several tools. All of these should be available to the developers as build targets and run in your continuous build. Lint and your unit tests should be run as part of your submission process.&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;lint - The bare minimum, it just detects syntax errors in your scripts. Code that doesn&#39;t pass the lint test won&#39;t pass any other tests or manual QA.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.phpunit.de/&quot;&gt;PHPUnit&lt;/a&gt; - Standard unit testing framework. There is plenty of information about it elsewhere.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://pear.php.net/package/PHP_CodeSniffer&quot;&gt;PHP Code Sniffer&lt;/a&gt; - Detects code smells that should be fixed. Many bad programming practices can be written as &quot;sniffs&quot; along with most rules from your smile guide.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://phpmd.org/&quot;&gt;PHP Mess Detector&lt;/a&gt; - Statically analyzes your code for possible bugs or coding practices that tends to hide bugs.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sebastianbergmann/phpcpd&quot;&gt;PHP Copy Paste Detector&lt;/a&gt; - Scans your code to find large similar blocks which can be factored out to a common method.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sebastianbergmann/phpdcd&quot;&gt;PHP Dead Code Detector&lt;/a&gt; - Scans your code to find code that can not be reached. For example, code after a return statement.&lt;/li&gt;
&lt;li&gt;Code coverage - Adding the &lt;a href=&quot;http://xdebug.org/&quot;&gt;xdebug&lt;/a&gt; extension to your system allows &lt;a href=&quot;http://www.phpunit.de/&quot;&gt;PHPUnit&lt;/a&gt; to calculate how much of your code is run by unit tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Code reviews&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
I talked about two different code review packages:&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.reviewboard.org/&quot;&gt;Review Board&lt;/a&gt; - You install it on your server and host it yourself.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://codereview.appspot.com/&quot;&gt;Codereview.appspot&lt;/a&gt; - Use Google&#39;s code review tool.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
And I talked about three different ways of doing reviews:&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;Pre-review - Code doesn&#39;t get submitted until a peer reviews it. Keeps bad stuff out of your code base.&lt;/li&gt;
&lt;li&gt;Post-review - Code gets submitted, then gets peer reviewed. Comments made about the code may never get resolved, but code reviews don&#39;t slow down getting code into production.&lt;/li&gt;
&lt;li&gt;Public shaming - Put the code up on a projector and discuss as a team. Great way to destroy programmer morale.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
I mentioned a few points about what to look for in a code review:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;anything the tools couldn&#39;t catch&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;logic errors (like ifs that don&#39;t make sense)&lt;/li&gt;
&lt;li&gt;loops with off-by-one errors&lt;/li&gt;
&lt;li&gt;performance problems (SQL in a loop)&lt;/li&gt;
&lt;li&gt;things to refactor (large methods)&lt;/li&gt;
&lt;/ul&gt;
&lt;li&gt;or things they missed&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Style problems (not really wrong, but you know, wrong)&lt;/li&gt;
&lt;li&gt;Typos (variable names, documentation)&lt;/li&gt;
&lt;li&gt;Tests that don&#39;t have assertions&lt;/li&gt;
&lt;li&gt;Methods without tests&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Style guides&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
There&#39;s two ways to choose a style guide:&lt;/div&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;Roll your own - Look at your existing code and build the style guide from what you&#39;re already doing.&lt;/li&gt;
&lt;li&gt;Use existing - Such as &lt;a href=&quot;http://framework.zend.com/manual/en/coding-standard.coding-style.html&quot;&gt;Zend&lt;/a&gt; or &lt;a href=&quot;http://pear.php.net/manual/en/standards.php&quot;&gt;Pear&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/2758922004546233359/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2011/11/building-rock-solid-software-in-real.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/2758922004546233359'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/2758922004546233359'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2011/11/building-rock-solid-software-in-real.html' title='Building rock solid software in the real world'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://farm1.static.flickr.com/22/32995286_16f7f96b20_t.jpg" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-8419245952863202185</id><published>2010-11-30T09:00:00.002-06:00</published><updated>2011-05-17T11:28:31.491-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ant"/><category scheme="http://www.blogger.com/atom/ns#" term="css"/><title type='text'>Using YUI compressor in an ant build process</title><content type='html'>Style sheets can get really large, really quickly. To make things easier to find, I split my CSS files out into major pieces of functionality. Instead of having to search through one giant style sheet of doom to find the rule that changes how a form field appears, I can open the style sheet for that bit of functionality. Many of my forms are customized, so each might have its own style sheet.&lt;br /&gt;
&lt;br /&gt;
So I may have a bunch of CSS files in my styles directory:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;general.css&lt;/li&gt;
&lt;li&gt;login.css&lt;/li&gt;
&lt;li&gt;signup.css&lt;/li&gt;
&lt;li&gt;profile.css&lt;/li&gt;
&lt;/ul&gt;Each rule in each of those files is namespaced by adding an ID attribute to the page&#39;s body tag, a class attribute to the body tag, or both. Then to deploy, the files need to be joined. I used to just concatenate the files together, but the YUI compressor does a great job making the files smaller that I started using that as well.&lt;br /&gt;
&lt;br /&gt;
Every page on my site requires /styles/c.css. To generate that file from the components, I use some ant rules.&lt;br /&gt;
&lt;br /&gt;
First, I make sure that I&#39;ve download the YUI compressor. Since I have more than one project that uses this same system, I download the YUI compressor to a common location. And since I&#39;m too lazy to worry about setting up my build environment on any system that I use I&#39;ve included the rules to actually download the library. This target will only download the library once. If you override the yuicompressor property you can move the jar to wherever you want. I stick mine in tmp.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;
&amp;lt;property name=&quot;yuicompressor&quot;
    value=&quot;/tmp/yuicompressor.jar&quot; /&amp;gt;
&amp;lt;available file=&quot;${yuicompressor}&quot;
    property=&quot;yuicompressor.exists&quot; /&amp;gt;
&amp;lt;target name=&quot;yui-compressor-download&quot;
        unless=&quot;yuicompressor.exists&quot;
        description=&quot;Download and unpack YUI compressor&quot;&amp;gt;
    &amp;lt;echo message=&quot;Downloading YUI compressor&quot; /&amp;gt;
    &amp;lt;exec executable=&quot;wget&quot;&amp;gt;
        &amp;lt;arg value=&quot;--no-clobber&quot; /&amp;gt;
        &amp;lt;arg value=&quot;--no-verbose&quot; /&amp;gt;
        &amp;lt;arg value=&quot;--output-document=/tmp/yuicompressor.zip&quot; /&amp;gt;
        &amp;lt;arg value=&quot;http://yui.zenfs.com/releases/yuicompressor/yuicompressor-2.4.6.zip&lt;/code&gt;&quot; /&amp;gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;    &amp;lt;/exec&amp;gt;
    &amp;lt;unzip src=&quot;/tmp/yuicompressor.zip&quot; dest=&quot;/tmp&quot;&amp;gt;
        &amp;lt;patternset&amp;gt;
            &amp;lt;include name=&quot;**/build/yuicompressor-2.4.6.jar&quot; /&amp;gt;
        &amp;lt;/patternset&amp;gt;
    &amp;lt;/unzip&amp;gt;
    &amp;lt;move file=&quot;/tmp/yuicompressor-2.4.6/build/yuicompressor-2.4.6.jar&quot;
          tofile=&quot;${yuicompressor}&quot; overwrite=&quot;true&quot; /&amp;gt;
    &amp;lt;delete file=&quot;/tmp/yuicompressor.zip&quot; /&amp;gt;
    &amp;lt;delete dir=&quot;/tmp/yuicompressor-2.4.6&quot; /&amp;gt;
&amp;lt;/target&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;Then, to actually build the combined and shrunk CSS file, I use a rule like this:&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;uptodate property=&quot;compile-css.notRequired&quot; targetfile=&quot;c.css&quot;&amp;gt;
    &amp;lt;srcfiles dir=&quot;styles&quot; includes=&quot;**/*css&quot; /&amp;gt;
&amp;lt;/uptodate&amp;gt;
&amp;lt;target name=&quot;compile-css&quot; depends=&quot;yui-compressor-download&quot;
        unless=&quot;compile-css.notRequired&quot;&amp;gt;
    &amp;lt;echo message=&quot;Compressing CSS&quot; /&amp;gt;
    &amp;lt;concat append=&quot;no&quot; destfile=&quot;style-combined.css&quot; force=&quot;no&quot;&amp;gt;
        &amp;lt;file file=&quot;styles/general.css&quot; /&amp;gt;
        &amp;lt;file file=&quot;styles/login.css&quot; /&amp;gt;
        &amp;lt;file file=&quot;styles/signup.css&quot; /&amp;gt;
        &amp;lt;file file=&quot;styles/profile.css&quot; /&amp;gt;
    &amp;lt;/concat&amp;gt;
    &amp;lt;java jar=&quot;${yuicompressor}&quot; fork=&quot;true&quot;&amp;gt;
        &amp;lt;arg value=&quot;style-combined.css&quot; /&amp;gt;
        &amp;lt;arg value=&quot;--charset=UTF-8&quot; /&amp;gt;
        &amp;lt;arg value=&quot;--verbose&quot; /&amp;gt;
        &amp;lt;arg value=&quot;-o&quot; /&amp;gt;
        &amp;lt;arg value=&quot;c.css&quot; /&amp;gt;
    &amp;lt;/java&amp;gt;
    &amp;lt;delete file=&quot;style-combined.css&quot; /&amp;gt;
&amp;lt;&lt;/code&gt;/target&amp;gt;&lt;/pre&gt;&lt;pre&gt;&lt;/pre&gt;&lt;pre&gt;If the YUI compressor has not been downloaded, it will call the download rule for it. Then it concatenates all of the styles together and compresses them. The uptodate rule makes sure that it only runs if one of the files has changes.&lt;/pre&gt;&lt;pre&gt;&lt;/pre&gt;&lt;pre&gt;&lt;b&gt;&lt;b&gt;Update:&lt;/b&gt; Changed the YUI version number to the newest version and re-added the code that actually builds the CSS. Not sure where it went.&lt;/b&gt;&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/8419245952863202185/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/11/using-yui-compressor-in-ant-build.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/8419245952863202185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/8419245952863202185'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/11/using-yui-compressor-in-ant-build.html' title='Using YUI compressor in an ant build process'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-972401019320590441</id><published>2010-10-13T15:01:00.001-05:00</published><updated>2011-05-17T11:47:42.925-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><title type='text'>A few rants on JavaScript</title><content type='html'>I&#39;ve got a few things to get off my chest relating to JavaScript.&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Google&#39;s Closure &lt;a href=&quot;http://code.google.com/closure/library/&quot;&gt;library&lt;/a&gt; and &lt;a href=&quot;http://code.google.com/closure/compiler/&quot;&gt;compiler&lt;/a&gt; kick ass. However, they need to be cock punched for naming it after a notable language feature. That means that if you want to do a search for something about the library or compiler you have to sift through a bunch of stuff relating to programming closures in a language to get to stuff about programming in Closure if you&#39;re searching for a particular topic.&lt;/li&gt;
&lt;li&gt;W3schools needs to update their site or take it down. It hasn&#39;t been updated in a decade or so. The web has moved on and they seem to have top spots in search results for so many topics.&lt;/li&gt;
&lt;li&gt;Technical recruiters need to understand that Java and &lt;a href=&quot;https://developer.mozilla.org/en/JavaScript/Guide&quot;&gt;JavaScript&lt;/a&gt; are not the same thing; proficiency in one does not mean proficiency in the other. I have JavaScript on my resume, but nothing about Java. Yet I always receive calls from recruiters for J2EE jobs and the like.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;a href=&quot;https://developer.mozilla.org/en/DOM/element.innerHTML&quot; title=&quot;JavaScript element.innerHTML&quot;&gt;&lt;img alt=&quot;JavaScript element.innerHTML&quot; height=&quot;150&quot; src=&quot;http://static.jsconf.us/promotejsh.gif&quot; width=&quot;180&quot; /&gt;&lt;/a&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/972401019320590441/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/10/few-rants-on-javascript.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/972401019320590441'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/972401019320590441'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/10/few-rants-on-javascript.html' title='A few rants on JavaScript'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-2219110202280519554</id><published>2010-10-06T13:27:00.000-05:00</published><updated>2010-10-06T13:27:58.311-05:00</updated><title type='text'>{water}Falling out of Agile</title><content type='html'>&lt;h2&gt;The waterfall approach&lt;/h2&gt;&lt;br /&gt;
My last two software development groups switched to Agile while I was part of them. In both cases, it was a relatively amazing experience for me. Instead of towards a long term goal and finding out just how far off the mark we had been from the start when we finished, we were able to change directions so quicker in the middle of development.&lt;br /&gt;
&lt;br /&gt;
If I was to plot a big project&#39;s path before we started with Agile, it would look something like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEip8ZFDu0AzJDl1WvZ6kdudElT_z9lIiPZKQeLZbKlK72rfj5LV0ah4sl875hyqtyRQkS2hjCW3CsmiI7IcdLfCWu2lkrOnuBruvRyffcfEsc4wqBoA5OxPt4AA2VZledMeK5aiWi8aWrHb/s1600/C:%5Cfakepath%5Cwaterfall.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEip8ZFDu0AzJDl1WvZ6kdudElT_z9lIiPZKQeLZbKlK72rfj5LV0ah4sl875hyqtyRQkS2hjCW3CsmiI7IcdLfCWu2lkrOnuBruvRyffcfEsc4wqBoA5OxPt4AA2VZledMeK5aiWi8aWrHb/s1600/C:%5Cfakepath%5Cwaterfall.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/div&gt;Each line in that graph is at least a month. At the end of the first phase, we had a usable product, but we realized that it really wasn&#39;t what our customer actually wanted. We eventually built what our customer actually wanted but it took close to a year, and we radically changed the product&#39;s direction several times.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;The agile approach&lt;/h2&gt;&lt;br /&gt;
Compare that to our path under agile:&lt;br /&gt;
&lt;br /&gt;
&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrF_IWPO5MMhUsJAbOrqspUj625Lh25uF_6W1C4_msXNZErtx2Pnnm5_92Sh_0WRTrIOqUb8ZJc0rJvLl5G4P49cvp725K-Pg_uQyWRiBRDS0aAIAQUBN7W_eAV97yyun79VFsjJAnU9Jd/s1600/C:%5Cfakepath%5Cagile.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrF_IWPO5MMhUsJAbOrqspUj625Lh25uF_6W1C4_msXNZErtx2Pnnm5_92Sh_0WRTrIOqUb8ZJc0rJvLl5G4P49cvp725K-Pg_uQyWRiBRDS0aAIAQUBN7W_eAV97yyun79VFsjJAnU9Jd/s1600/C:%5Cfakepath%5Cagile.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;
&lt;/div&gt;&lt;/div&gt;Each segment is two weeks. At the end of the first iteration we had a somewhat usable product, but we realized then that we were already headed in the wrong direction. So we changed the product&#39;s direction early and often. Note that at each step our goal is the same as a phase in the waterfall method, but we change direction way before we reach our goal. Instead of taking over a year, the project took fourteen weeks. The development team was happier since we did not have to throw out huge chunks of code that we had written, and the business owners were happier since they could see our progress towards a goal.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Back to the stone age with you!&lt;/h2&gt;&lt;br /&gt;
My current project is using more of a waterfall approach to designing an API. Instead of biting off tiny chunks of the API for development and building the client to go along with it, we&#39;re building the API out fully first, then building the clients to consume the API.&lt;br /&gt;
&lt;br /&gt;
So far this has already led to lots of wasted effort. If I think I understand the business case for something and build an API for it, the API will work awesome until I try to use it for a real-world test. Then I find out that the definition of a concept I was operating under is wrong. So then to get the client to successfully consume the API I have to write the client and change the API at the same time.&lt;br /&gt;
&lt;br /&gt;
We had a contractor work for three months on a large segment of the API that we ended up completely not needing. While working in a more Agile way wouldn&#39;t have kept him from writing all of the few thousand lines of code that has been since deleted, it would have shown us much earlier that he was going down the wrong path and at least some of his work may have been saved.&lt;br /&gt;
&lt;br /&gt;
Agile wants a usable piece at the end of each iteration. That requires building at least an example client along with the API.</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/2219110202280519554/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/10/waterfalling-out-of-agile.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/2219110202280519554'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/2219110202280519554'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/10/waterfalling-out-of-agile.html' title='{water}Falling out of Agile'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEip8ZFDu0AzJDl1WvZ6kdudElT_z9lIiPZKQeLZbKlK72rfj5LV0ah4sl875hyqtyRQkS2hjCW3CsmiI7IcdLfCWu2lkrOnuBruvRyffcfEsc4wqBoA5OxPt4AA2VZledMeK5aiWi8aWrHb/s72-c/C:%5Cfakepath%5Cwaterfall.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-6444953099593116439</id><published>2010-09-28T08:00:00.003-05:00</published><updated>2011-06-17T11:18:23.066-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="curl"/><category scheme="http://www.blogger.com/atom/ns#" term="php"/><category scheme="http://www.blogger.com/atom/ns#" term="phpunit"/><category scheme="http://www.blogger.com/atom/ns#" term="testing"/><title type='text'>Unit Testing cURL Code in PHP</title><content type='html'>My team is writing an API that will be used from other parts of our infrastructure. We&#39;re using &lt;a href=&quot;http://us3.php.net/curl&quot;&gt;cURL&lt;/a&gt; to make connections from the other parts to the API. But sometimes our network and API server are a bit flaky, which makes code that calls the API brittle if we don&#39;t handle those transient errors.&lt;br /&gt;
&lt;br /&gt;
Instead of using PHP&#39;s native curl_* functions, we&#39;ve created a simple class to wrap them. This helps some of the more junior team members as well, hiding some of the nitty-gritty details of making HTTP connections:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;/code&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Class abstracting the cURL library for easier use.
 *
 * Usage:
 *     $curl = new Curl();
 *     $curl-&amp;gt;setUrl(&#39;http://www.google.com/#&#39;)
 *         -&amp;gt;setData(&#39;&amp;amp;q=testing+curl&#39;)
 *         -&amp;gt;setType(&#39;GET&#39;);
 *     $curl-&amp;gt;send();
 *     echo $curl-&amp;gt;getStatusCode(), PHP_EOL;
 *     echo $curl-&amp;gt;getResponse(), PHP_EOL;
 */
class Curl {
    /**
     * @var string Body returned by the last request.
     */
    protected $body;

    /**
     * @var resource Actual CURL connection handle.
     */
    protected $ch;

    /**
     * @var mixed Data to send to server.
     */
    protected  $data;

    /**
     * @var integer Response code from the last request.
     */
    protected $status;

    /**
     * @var string Request type.
     */
    protected $type;

    /**
     * @var string Url for the connection.
     */
    protected $url;


    /**
     * Constructor.
     */
    public function __construct() {
        $this-&amp;gt;body = null;
        $this-&amp;gt;ch = curl_init();
        $this-&amp;gt;data = null;
        $this-&amp;gt;status = null;
        $this-&amp;gt;type = &#39;GET&#39;;
        $this-&amp;gt;url = null;
        curl_setopt($this-&amp;gt;ch, CURLOPT_RETURNTRANSFER,
            true);
        curl_setopt($this-&amp;gt;ch, CURLOPT_USERAGENT,
            &#39;Curl Client&#39;);
    }

    /**
     * Return the body returned by the last request.
     * @return string
     */
    public function getBody() {
        return $this-&amp;gt;body;
    }

    /**
     * Return the current payload.
     * @return mixed
     */
    public function getData() {
        return $this-&amp;gt;data;
    }

    /**
     * Set the payload for the request.
     *
     * This can either by a string, formatted like a query
     * string:
     *      foo=bar&amp;amp;mitz=fah
     * or a single-dimensional array:
     *      array(&#39;foo&#39; =&amp;gt; &#39;bar&#39;, &#39;mitz&#39; =&amp;gt; &#39;fah&#39;)
     * @param mixed $data
     * @return Curl
     */
    public function setData($data) {
        if (is_array($data)) {
            $data = http_build_query($data);
        }
        $this-&amp;gt;data = $data;
        return $this;
    }

    /**
     * Return the status code for the last request.
     * @return integer
     */
    public function getStatusCode() {
        return $this-&amp;gt;status;
    }

    /**
     * Return the current type of request.
     * @return string
     */
    public function getType() {
        return $this-&amp;gt;type;
    }

    /**
     * Set the type of request to make (GET, POST, PUT,
     * DELETE, etc)
     * @param string $type Request type to send.
     * @return Curl
     */
    public function setType($type) {
        $this-&amp;gt;type = $type;
        curl_setopt($this-&amp;gt;ch, CURLOPT_CUSTOMREQUEST,
            $type);
        return $this;
    }

    /**
     * Return the connection&#39;s URL.
     * @return string
     */
    public function getUrl() {
        return $this-&amp;gt;url;
    }

    /**
     * Set the URL to make an HTTP connection to.
     * @param string $url URL to connect to.
     * @return Curl
     */
    public function setUrl($url) {
        $this-&amp;gt;url = $url;
        curl_setopt($this-&amp;gt;ch, CURLOPT_URL, $url);
        return $this;
    }

    /**
     * Send the request.
     * @return Curl|null
     */
    public function send() {
        if (!$this-&amp;gt;url) {
            return null;
        }
        if (&#39;GET&#39; == $this-&amp;gt;type) {
            $this-&amp;gt;url .= &#39;?&#39; . $this-&amp;gt;data;
        } else {
            curl_setopt($this-&amp;gt;ch, CURLOPT_POSTFIELDS,
                $this-&amp;gt;data);
            if (&#39;PUT&#39; == $this-&amp;gt;type) {
                $header = &#39;Content-Length: &#39;
                    . strlen($this-&amp;gt;data);
                curl_setopt($this-&amp;gt;ch, CURLOPT_HTTPHEADER,
                    array($header));
            }
        }
        $this-&amp;gt;body = curl_exec($this-&amp;gt;ch);
        $this-&amp;gt;status = curl_getinfo($this-&amp;gt;ch,
            CURLINFO_HTTP_CODE);
        return $this;
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
First, here&#39;s an example class that calls to the API to pull a customer record. Assume the API returns a JSON object when called with http://api/customer/42. We pass in a Curl object to actually make the connection, then parse the response to fill our customer object&#39;s members.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;/code&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class Customer {
    /**
     * @var integer Database ID.
     */
    public $id;

    /**
     * @var string Customer&#39;s name.
     */
    public $name;

    /**
     * Load a customer from the API.
     * @param integer $id ID of the customer to load.
     * @param Curl $curl Curl object.
     */
    public function load($id, Curl $curl) {
        $curl-&amp;gt;setUrl(&#39;http://api/customer/&#39; . (int)$id)
            -&amp;gt;send();
        $customer = json_decode($curl-&amp;gt;getResponse());
        $this-&amp;gt;id = $customer-&amp;gt;id;
        $this-&amp;gt;name = $customer-&amp;gt;name;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
In a perfect world, calling load() will always populate the id and name members of the customer. In the real world, the API server could be down, or the network could die, or random alpha particles could mess up the request, so the code should handle those error messages. But setting up a test network to create those conditions is expensive and probably a waste of time, so we&#39;ll create a stub for the Curl object that allows us to break the API calls in interesting ways.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;/code&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Stub for the Curl class.
 *
 * Allows testing code that uses cURL without actually
 * making any HTTP connections.
 */
class StubCurl
extends Curl {
    /**
     * Constructor.
     */
    public function __construct() {
        $this-&amp;gt;body = null;
        $this-&amp;gt;data = null;
        $this-&amp;gt;status = null;
        $this-&amp;gt;type = &#39;GET&#39;;
        $this-&amp;gt;url = null;
    }

    /**
     * Set the response &#39;returned&#39; by the server.
     * @param string $body
     * @return StubCurl
     */
    public function setBody($body) {
        $this-&amp;gt;body = $body;
    }

    /**
     * Set the HTTP status &#39;returned&#39; by the server.
     * @param integer $code
     * @return StubCurl
     */
    public function setStatusCode($status) {
        $this-&amp;gt;status = $status;
    }

    /**
     * Set the request method (GET, POST, etc).
     * @param string $type
     * @return StubCurl
     */
    public function setType($type) {
        $this-&amp;gt;type = $type;
        return $this;
    }

    /**
     * Set the URL to connect to.
     * @param string $url
     * @return StubCurl
     */
    public function setUrl($url) {
        $this-&amp;gt;url = $url;
        return $this;
    }

    /**
     * &#39;Send&#39; the cURL request.
     *
     * Obviously doesn&#39;t actually send any requests.
     * @return StubCurl
     */
    public function send() {
        if (!$this-&amp;gt;url) {
            return null;
        }
        if (&#39;GET&#39; == $this-&amp;gt;type) {
            $this-&amp;gt;url .= &#39;?&#39; . $this-&amp;gt;data;
        }
        return $this;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
By passing a StubCurl object in to Customer::Load(), we can make the server return any status (or no status at all):&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;/code&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;require_once &#39;Curl.php&#39;;
require_once &#39;Customer.php&#39;;
require_once &#39;StubCurl.php&#39;;

class CustomerTest
extends PHPUnit_Framework_TestCase {
    private $customer;

    protected function setUp() {
        $this-&amp;gt;customer = new Customer();
    }

    /**
     * Test loading with a good API connection.
     * @covers Customer::load
     * @test
     */
    public function testLoadFound() {
        $curl = new StubCurl();
        $curl-&amp;gt;setStatusCode(200)
            -&amp;gt;setBody(&#39;{&quot;id&quot;:42,&quot;name&quot;:&quot;Bob King&quot;}&#39;);
        $this-&amp;gt;customer-&amp;gt;load(42, $curl);
        $this-&amp;gt;assertEquals(&#39;Bob King&#39;,
            $this-&amp;gt;customer-&amp;gt;name);
    }

    /**
     * Test loading a customer that isn&#39;t found.
     * Add a test for whatever behavior you want the
     * customer object to take if the API returns a 404
     * Not Found error. In this case, we&#39;re going to want
     * the customer object to throw an exception.
     * @covers Customer::load
     * @expectedException NotFoundException
     * @test
     */
    public function testLoadNotFound() {
        $curl = new StubCurl();
        $curl-&amp;gt;setStatusCode(404);
        $this-&amp;gt;customer-&amp;gt;load(42, $curl);
    }

    /**
     * Test loading a customer from bad JSON.
     * The API returns a garbled response body.
     * @covers Customer::load
     * @expectedException BadResponseException
     * @test
     */
    public function testBadResponse() {
        $curl = new StubCurl();
        $curl-&amp;gt;setStatusCode(200)
            -&amp;gt;setBody(&#39;{&quot;id&quot;:_*()*(*$#@#$&#39;);
        $this-&amp;gt;customer-&amp;gt;load(42, $curl);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Then the Customer class needs to have its load() method beefed up to handle the errors:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;/code&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Load a customer from the API.
 * @param integer $id ID of the customer to load.
 * @param Curl $curl Curl object.
 * @throws BadResponseException
 * @throws NotFoundException
 */
public function load($id, Curl $curl) {
    $curl-&amp;gt;setUrl(&#39;http://api/customer/&#39; . (int)$id)
        -&amp;gt;send();
    if (404 == $curl-&amp;gt;getStatusCode()) {
        throw new NotFoundException();
    }
    $customer = json_decode($curl-&amp;gt;getResponse());
    if (null === $customer) {
        throw new BadResponseException();
    }
    $this-&amp;gt;id = $customer-&amp;gt;id;
    $this-&amp;gt;name = $customer-&amp;gt;name;
}
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Obviously you&#39;ll want to handle more errors in a production environment, maybe logging bad responses and server errors for later analysis.</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/6444953099593116439/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/09/unit-testing-curl-code-in-php.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/6444953099593116439'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/6444953099593116439'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/09/unit-testing-curl-code-in-php.html' title='Unit Testing cURL Code in PHP'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-3387247910314864813</id><published>2010-09-21T08:00:00.008-05:00</published><updated>2012-02-06T10:24:23.747-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="subversion"/><title type='text'>Catching Subversion conflicts on commit</title><content type='html'>If two people work on the same file in the same place in the file, Subversion can get a bit confused about how to merge the two different pieces, and will prompt the user to help it sort things out. However, it is up to the user to actually do so, and they&#39;re perfectly able to try to submit that file with the conflict markers still in the file. This is a pre-submit hook to not let people shoot themselves (and your codebase) in the foot.&lt;br /&gt;&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/1753090.js&quot;&gt; &lt;/script&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/3387247910314864813/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/09/catching-subversion-conflicts-on-commit.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/3387247910314864813'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/3387247910314864813'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/09/catching-subversion-conflicts-on-commit.html' title='Catching Subversion conflicts on commit'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-1248660060392165800</id><published>2010-09-14T08:00:00.004-05:00</published><updated>2012-02-06T10:30:17.023-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="php"/><category scheme="http://www.blogger.com/atom/ns#" term="subversion"/><title type='text'>Avoiding PHP syntax errors with Subversion pre-commit hooks</title><content type='html'>Since my &lt;a href=&quot;http://omni-spot.blogspot.com/2010/09/integrating-subversion-and-review-board.html&quot;&gt;last&lt;/a&gt; &lt;a href=&quot;http://omni-spot.blogspot.com/2010/08/forcing-phpunit-tests-on-subversion.html&quot;&gt;few&lt;/a&gt; &lt;a href=&quot;http://omni-spot.blogspot.com/2010/07/setting-up-bug-tracker-regexes-in.html&quot;&gt;posts&lt;/a&gt; have all been about Subversion, I figured I&#39;d do another. Our new team member is an experienced programmer, but has not had any experience with PHP. And he&#39;s not real good about actually running his code before submitting it.&lt;br /&gt;
&lt;br /&gt;
PHP allows you to run its command line executable with a -l flag to catch any syntax errors in your code. Very simple, but can really help catch some stupid errors. Yes, they should definitely be caught by a code review, but code reviewers should also be able to assume that the code actually runs.&lt;br /&gt;
&lt;br /&gt;
So here&#39;s a Subversion pre-commit hook that will run PHP lint on all PHP files and reject the commit if any of them fail.&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/1753122.js&quot;&gt; &lt;/script&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/1248660060392165800/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/09/avoiding-php-syntax-errors-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/1248660060392165800'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/1248660060392165800'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/09/avoiding-php-syntax-errors-with.html' title='Avoiding PHP syntax errors with Subversion pre-commit hooks'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-233686341989292463</id><published>2010-09-07T09:05:00.002-05:00</published><updated>2012-02-06T10:28:30.664-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="code review"/><category scheme="http://www.blogger.com/atom/ns#" term="review board"/><category scheme="http://www.blogger.com/atom/ns#" term="subversion"/><title type='text'>Integrating Subversion and Review Board</title><content type='html'>At Google, all production code must be peer reviewed before it is allowed to be submitted to their source control system. Since I&#39;m no longer a Googler, I don&#39;t have access to their awesome infrastructure so I had to roll my own. &lt;a href=&quot;http://reviewboard.org/&quot;&gt;Review Board&lt;/a&gt; is a pretty good code review system, and unlike the open source implementation of Google&#39;s internal one, you can host Review Board on your own hardware.&lt;br /&gt;&lt;br /&gt;
We use Subversion as our SCM. Out of the box, Subversion doesn&#39;t force code to be reviewed before submission, and I couldn&#39;t find any documentation about forcing code reviews through pre-submit hooks. Subversion&#39;s hooks are pretty powerful, and Review Board has a decent API, so I wrote my own. Hopefully this is useful to someone out there. It&#39;s written in PHP5. Put this in your subversion hooks directory, call it pre-submit, and make it executable.&lt;br /&gt;&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/1753112.js&quot;&gt; &lt;/script&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/233686341989292463/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/09/integrating-subversion-and-review-board.html#comment-form' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/233686341989292463'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/233686341989292463'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/09/integrating-subversion-and-review-board.html' title='Integrating Subversion and Review Board'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-6041231430535805472</id><published>2010-08-23T11:06:00.001-05:00</published><updated>2012-02-06T10:20:02.584-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="php"/><category scheme="http://www.blogger.com/atom/ns#" term="phpunit"/><category scheme="http://www.blogger.com/atom/ns#" term="programming"/><category scheme="http://www.blogger.com/atom/ns#" term="subversion"/><category scheme="http://www.blogger.com/atom/ns#" term="testing"/><title type='text'>Forcing PHPUnit tests on Subversion commit</title><content type='html'>All of my PHP projects use &lt;a href=&quot;http://www.phpunit.de/&quot;&gt;PHPUnit&lt;/a&gt; for unit testing. One of my coworkers just refuses to understand the point of testing. He doesn&#39;t get Test Driven Development (TDD), and rarely runs the unit tests for his classes. This means that he frequently breaks my build. I can&#39;t force him to change his behavior since he doesn&#39;t work for me, but I can change the way my systems work. And I control the Subversion server.&lt;br /&gt;
I wanted to force unit tests to pass before allowing submission, but couldn&#39;t find anything about running PHPUnit tests in a Subversion pre-commit hook. So I wrote my own. Hopefully this will help someone out there:&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/1753066.js&quot;&gt;&lt;/script&gt;
To use this, copy it to the hooks directory in your Subversion repository and name it &#39;pre-commit&#39;. Make sure the paths defined in the script are correct for your system. Make it executable (chmod +x pre-commit).</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/6041231430535805472/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/08/forcing-phpunit-tests-on-subversion.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/6041231430535805472'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/6041231430535805472'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/08/forcing-phpunit-tests-on-subversion.html' title='Forcing PHPUnit tests on Subversion commit'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-7239608658815868768</id><published>2010-07-22T10:49:00.000-05:00</published><updated>2010-07-22T11:38:19.472-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="subversion"/><category scheme="http://www.blogger.com/atom/ns#" term="websvn"/><title type='text'>Setting up bug tracker regexes in WebSVN</title><content type='html'>A while ago I set up WebSvn (&lt;a href=&quot;http://www.websvn.info/&quot;&gt;http://www.websvn.info/&lt;/a&gt;) to allow viewing my Subversion repository at work.  Today I decided to make it hack the commit messages in order to change bug links into hyperlinks to my bug tracker. After looking at the code a bit, there was a configuration option to turn on &#39;bugtraq&#39; which claims to do what I want, but the documentation on how to set it all up was a bit non-existent. This post is mainly for me to remember how to set this up at any future employer that I might need it at.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To enable the log message hacking, edit include/config.php and set &quot;$config-&gt;setBugtraqEnabled(true);&quot;. This will turn on the code that tries to parse log messages for all repositories that you&#39;ve got set up. If you want to turn it on for a subset of your repositories you can add lines like &quot;$config-&gt;setBugtraqEnabled(true, &#39;repository-name&#39;);&quot; or turn them off the same way.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;However, there doesn&#39;t appear to be documentation there about actually configuring where the links should go, or how you format the bug labels in your commit messages. My commit messages for the current project have used many different bug labels throughout its history:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;bug: 12&lt;/li&gt;&lt;li&gt;bugs: 12, 34&lt;/li&gt;&lt;li&gt;Fixed lots of bugs (bugs: 12, 34)&lt;/li&gt;&lt;li&gt;Issue #12&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;It would be nice to catch them all.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After Googling for some information about how to set all this up, I realized that WebSVN uses Subversion properties for configuring the regexes needed instead of putting them in the config file with the rest of the repository configuration. Here&#39;s the subversion commands I used to set up the log munging to link to my bug tracking system:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;$ svn propset bugtraq:logregex &#39;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space:pre&quot;&gt;(([Bb]ug[s]?)|([Ii]ssue))+[:]? [#]?(\d+)(?:,? ?#?(\d+))*&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space:pre&quot;&gt;&lt;/span&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space:pre&quot;&gt;(&lt;/span&gt;\d+)&#39; file:///opt/svn/my_repo&lt;/div&gt;&lt;div&gt;$ svn propset bugtraq:url http://tracker.domain.com/show_bug.cgi?id=%BUGID% file:///opt/svn/my_repo&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Note that the first one spans a line. WebSVN uses the first line to find the bugs list, and the second one to find individual bug numbers from within the larger list. %BUGID% will be replaced in the URL with the bug id.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Also note that if you use the typical trunk, branches, tags setup, you can &lt;b&gt;not&lt;/b&gt; just set the properties in the root of your source tree if you have trunk checked out. It needs to be set at the root of the repository or it will not work.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hopefully this will save someone some time trying to set up the log munging without losing too much hair.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/7239608658815868768/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/07/setting-up-bug-tracker-regexes-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/7239608658815868768'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/7239608658815868768'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/07/setting-up-bug-tracker-regexes-in.html' title='Setting up bug tracker regexes in WebSVN'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1127830764822879554.post-7821366078495824811</id><published>2010-02-22T14:35:00.000-06:00</published><updated>2010-03-01T08:13:33.822-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="framework"/><category scheme="http://www.blogger.com/atom/ns#" term="php"/><category scheme="http://www.blogger.com/atom/ns#" term="programming"/><title type='text'>Thoughts on Frameworks</title><content type='html'>&lt;div&gt;Every developer thinks that their way of coding is better than every other programmer out there. They think their APIs are more elegant, their style is more succinct, and their loops are more optimized.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;They are wrong.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I&#39;ve had the misfortune to work with several custom-built frameworks. They were all terrible in their own way. Some had a slight excuse in that they were built in aging versions of PHP, but the otherall crappiness of the framework still stands on its own. And I can&#39;t claim that I think they frameworks were utter shit because I didn&#39;t write them, since I did write one of them.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But in writing my own framework and realizing just how crappy it ended up gave me the perspective to realize that I should use one of the big frameworks. I should use a &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt;, or &lt;a href=&quot;http://cakephp.org/&quot;&gt;Cake&lt;/a&gt;, or &lt;a href=&quot;http://www.symfony-project.org/&quot;&gt;Symfony&lt;/a&gt;, or whatever. The programmers that wrote those frameworks may not individually be a much better developer than me, in aggregate they tower head and shoulders above. They get the benefit of having security experts picking at the seams of their interfaces, optimization guys fine tuning little used paths, and usability experts chiming in on workflow. They get the benefit of people trying to run the frameworks on operating systems you&#39;ve never heard of, trying things with them you&#39;ve never thought of, and using browsers you&#39;re too good to use.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Every developer &lt;b&gt;should&lt;/b&gt; write their own framework, throw it away, and use one of the established ones.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It&#39;s so tempting to have the control over every aspect of the framework and the complete understanding of how each moving piece fits together. It&#39;s nice being able to custom build functionality for each small piece of your application into the framework-level code. But what always happens is that you start a new project and reuse your framework since you&#39;ve put so much work into it. Then you find yourself having to code around all of the special cases that you put into the original. You&#39;re better off just using a more general framework from the start and putting your business logic where it belongs.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://omni-spot.blogspot.com/feeds/7821366078495824811/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://omni-spot.blogspot.com/2010/02/thoughts-on-frameworks.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/7821366078495824811'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1127830764822879554/posts/default/7821366078495824811'/><link rel='alternate' type='text/html' href='http://omni-spot.blogspot.com/2010/02/thoughts-on-frameworks.html' title='Thoughts on Frameworks'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04646989103891205931</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>