<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://davidsommers.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://davidsommers.com/" rel="alternate" type="text/html" /><updated>2026-07-17T11:53:12-04:00</updated><id>https://davidsommers.com/feed.xml</id><title type="html">David Sommers Blog</title><subtitle>David Sommers on software engineering, web performance, and the occasional yak shave.</subtitle><entry><title type="html">I replaced UIPath in a weekend</title><link href="https://davidsommers.com/2026/07/15/i-replaced-uipath-in-a-weekend.html" rel="alternate" type="text/html" title="I replaced UIPath in a weekend" /><published>2026-07-15T00:00:00-04:00</published><updated>2026-07-15T00:00:00-04:00</updated><id>https://davidsommers.com/2026/07/15/i-replaced-uipath-in-a-weekend</id><content type="html" xml:base="https://davidsommers.com/2026/07/15/i-replaced-uipath-in-a-weekend.html"><![CDATA[<p><strong>TL;DR</strong></p>

<ul>
  <li>On a Friday evening in June, our customer’s RPA platform (UIPath) that writes their staff’s schedule back into their source system failed. It was reported after hours — and the team that owns it doesn’t monitor it on weekends or after hours. So it sat dark, unattended, all weekend.</li>
  <li><strong>It never reported a single error.</strong> 592 tasks over five and a half days, zero failures logged. The bot accepted every job and picked up none of them. Inbound traffic looked perfectly normal. Their dashboards were green.</li>
  <li>We knew it broke right away, because a separate service we’d built months earlier doesn’t ask the bot whether the bot succeeded — it re-imports the schedule from the source system and checks what’s actually true. Our monitoring system lit up on Friday night while their dashboards were green.</li>
  <li>Over that weekend, with Claude and Codex as pair programmers, I built <strong>Scribe</strong> — a service that performs the same write-backs UIPath did. First code was in production at 2:33 AM Monday. A build that would normally take weeks took a weekend.</li>
  <li>We never turned it off. Five weeks later, with UIPath fully healthy, <strong>17.7% of its writes still need Scribe to fix them.</strong> The outage was just the loud version of a problem we’d been fixing manually ourselves.</li>
</ul>

<hr />

<h2 id="the-dependency-we-didnt-control">The dependency we didn’t control</h2>

<p>A bit of context on what we do. Our platform provides an AI scheduling assistant that manages healthcare staffing schedules — swaps, coverage, pickups, partial shifts, schedule changes. When a change is finalized in our system, it has to be written back into the customer’s <em>source</em> scheduling system, the system of record.</p>

<p>The vendor for that source system is building out a proper API, but it isn’t ready yet. So in the meantime, the write-back happens the way a human would do it: by driving the scheduling UI. And because of our customer’s requirements, those writes don’t run on our infrastructure — they’re routed to <strong>their</strong> RPA team, who own and operate a fleet of UIPath bots. We hand them the work; their bots type it in.</p>

<p>That’s a clean separation of duties on paper. In practice it means a critical step in our pipeline runs on a platform we can see but cannot fix — operated by a team on a different schedule than ours.</p>

<h2 id="the-friday-night-nobody-was-watching">The Friday night nobody was watching</h2>

<p>On Friday, June 5, the RPA system failed. The report came in after hours. And here’s the detail that matters most: the team that owns that platform doesn’t monitor it on weekends or after hours.</p>

<p>So the failure just… sat there. From Friday evening until Monday morning, the thing responsible for writing our users’ schedule changes into their system of record was completely dark, and the only people who could fix it weren’t looking. The root cause was on the UIPath side — the same class of infrastructure failure described in <a href="https://www.linkedin.com/posts/kaplann-ahmet_uipath-rpa-automation-activity-7470046977079119872-3q-r">this writeup</a> — and it would take their IT team <strong>a week</strong> to get the fix in place.</p>

<h2 id="how-we-knew-the-confirmation-service">How we knew: the confirmation service</h2>

<p>Months before Scribe existed, we’d built something unglamorous: a confirmation service that independently verifies the round trip.</p>

<p>The logic is simple. Our system finalizes a schedule change. UIPath writes it into the source system. Then, separately, we re-import the schedule <em>from</em> the source system on an ongoing basis. The confirmation service checks that every finalized request actually shows up in that re-import. If a change doesn’t materialize within a threshold, it alerts — faster when the affected shift starts soon, because an unwritten shift starting in two hours is a genuine staffing emergency, not a data quality nit.</p>

<p>Crucially, it doesn’t trust the writer’s own report of success. It doesn’t ask UIPath whether UIPath succeeded. It asks the <em>system of record</em> what’s actually true, and compares. That independence is exactly why it worked when the bot was dead: a failed write and a lying write look identical to that service, and both trip the alarm.</p>

<p>As our monitoring service flagged issues, we would route them to the original RPA team to beef up their bot and manually fix the source system ourselves. The team took turns clearing daily alerts. Sometimes we’d have one alert and sometimes a few but it always seemed manageable.</p>

<p>So on a Friday night, with nobody at the RPA team watching, our confirmation alerts told us the truth — nothing was landing. Not some things. Nothing. It wasn’t the 1-2 issues we’d normally see, it was pouring in. That’s how we knew UIPath was completely down.</p>

<h3 id="the-number-that-still-bothers-me">The number that still bothers me</h3>

<p>I went back and queried production while writing this, and the result is worth sitting with.</p>

<p>Across the five and a half days UIPath was down, <strong>592 RPA tasks (aka scheduling changes) were created. Exactly zero of them failed.</strong></p>

<p>Not a single <code class="language-plaintext highlighter-rouge">error</code>. Not a single <code class="language-plaintext highlighter-rouge">abandoned</code>. The bot never threw. It never reported a problem. The system was hard down — the jobs weren’t attempted and botched, they were accepted into the queue, moved to <code class="language-plaintext highlighter-rouge">NEW</code>, and then never picked up at all. If you had been watching UIPath’s own status reporting that weekend, you would have seen a clean board.</p>

<p>You can watch it flatline in the completion timestamps:</p>

<table>
  <thead>
    <tr>
      <th>Date</th>
      <th>Tasks completed</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Thu, Jun 4</td>
      <td>135</td>
    </tr>
    <tr>
      <td><strong>Fri, Jun 5</strong></td>
      <td><strong>119</strong> — last one at 20:27 UTC</td>
    </tr>
    <tr>
      <td>Sat, Jun 6</td>
      <td>0</td>
    </tr>
    <tr>
      <td>Sun, Jun 7</td>
      <td>0</td>
    </tr>
    <tr>
      <td>Mon, Jun 8</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p>Normal throughput was ~135 a day. The last completion of the pre-outage era landed Friday afternoon, and then the queue simply stopped draining. Not slowly. Not with errors. It just stopped.</p>

<p>There was no error to catch and no exception to page on. The only observable difference between “working perfectly” and “completely dead” was whether the work actually appeared in the source system — which is to say, the only thing that could tell the difference was a system that went and <em>looked</em>.</p>

<p>This is the thing I’d put on a poster: <strong>the failure was invisible in every system that reported on itself, and obvious in the one system that checked reality.</strong> A silent failure and a successful write look identical from the inside. They look completely different from the outside, if you bother to look from the outside.</p>

<p>That service is the unsung hero here. Everything that follows was possible because we already had ground truth.</p>

<h2 id="the-weekend">The weekend</h2>

<p>I had a choice. Wait an unknown number of days for someone else’s unmonitored outage to get noticed and fixed, or build the write-back myself.</p>

<p>We already had the hard-won knowledge of <em>how</em> to drive the source system’s UI — that’s the exact knowledge UIPath encoded. What we didn’t have was a service to run it. So over the weekend of June 6–7, I sat down with Claude and Codex and built one. (Why both: that’s a story for another blog post.)</p>

<p>The work was not trivial: browser automation against a finicky, hydration-heavy scheduling UI; a task-routing layer; a verification loop; a full session lifecycle with retries, alerting, and stuck-session recovery. On my own that’s a multi-week project — most of it the slow, unglamorous work of handling the twentieth weird edge case in a web UI that wasn’t built to be automated.</p>

<p>With AI in the loop, the ratio inverted. I spent my time deciding <em>what</em> the system should do and reviewing whether each piece was correct; the models handled the mechanical breadth — the selectors, the retry scans, the confirmation logic, the tests. Scribe’s first code was running in production at <strong>2:33 AM Monday</strong> — the headless-browser worker image, then the add-shift automation, then the post-create confirmation logic. By Monday afternoon the full verify-and-remediate service was deployed and we were processing messages on behalf of the RPA team.</p>

<p>What made the weekend possible wasn’t just fast code generation — it was a fast <em>loop</em>. Every commit deployed to production within minutes, so I wasn’t writing against a mental model of how the source system’s UI behaved; I was writing against how it actually behaved, right now, with real schedule data flowing through. Find an edge case, fix it, ship it, watch the next session, repeat. Over the following days that loop produced dozens of small, surgical hardening deploys — late-hydrating grid cells, clobbered time fields, detached iframes, locked schedules — each live minutes after I understood the problem. And by <em>I</em>, I mean a looped skill that reviewed recent Scribe Sessions, built a fix, automated a test through the training environment and then opened a PR. That cadence, with AI absorbing the mechanical cost of each fix, is what let a weekend build survive contact with production.</p>

<p>I want to be precise, because “AI built it in a weekend” is usually exaggerated. I still made every architectural decision. But the calendar doesn’t lie: a build that would have eaten weeks took a weekend, and it went into production handling real schedule writes during a real outage.</p>

<h2 id="giving-it-back-and-taking-it-right-back">Giving it back (and taking it right back)</h2>

<p>On June 10, the customer’s team thought UIPath was ready. We flipped the bypass off and handed the writes back.</p>

<p>It still wasn’t right. We hit issues immediately and flipped the bypass <strong>back on</strong> — Scribe resumed, and we kept going. It wasn’t until June 11 that UIPath genuinely took back over.</p>

<p>That failed handoff is why the next part exists. Recovery isn’t a moment, it’s a negotiation — and I no longer wanted our users’ schedules depending on someone else’s declaration that things were fine.</p>

<h2 id="meanwhile-the-roadmap-didnt-stop">Meanwhile, the roadmap didn’t stop</h2>

<p>Here’s the detail that makes me laugh now and did not make me laugh then.</p>

<p>We had a major feature launching that same week: a new <strong>Nurse Residency</strong> program, in development since early May. It pre-schedules an entire year of classes, up front, for every incoming resident across every cohort. It is exactly the kind of launch you want to happen on a calm week.</p>

<p>It went out on <strong>Thursday, June 11 at 3:09 PM</strong> — about six hours after UIPath had finally, genuinely taken back over that morning. In one shot it created <strong>3,096 class shifts across 10 cohorts</strong>, which became 3,085 shift-adds, which became <strong>3,922 RPA write-back tasks</strong>.</p>

<p>For scale: the entire outage — five and a half days of a dead platform — produced 592 tasks. The residency launch produced almost seven times that in an afternoon.</p>

<p>And it went through Scribe. 3,704 of those write-backs were routed to the bot we’d finished building 72 hours earlier, on a system whose first line of code had existed for three days. That’s the day I stopped thinking of Scribe as an emergency measure. An emergency measure gets you through the emergency; it doesn’t absorb your biggest feature change that month three days after it was written.</p>

<p>I’d love to claim we planned it that way. We didn’t. The launch date and the outage had nothing to do with each other; they just collided. But that’s sort of the point: you don’t get to schedule the week when your dependencies fail, and you don’t get to pause your roadmap while they sort it out.</p>

<h2 id="belts-suspenders-and-another-pair-of-suspenders">Belts, suspenders, and another pair of suspenders</h2>

<p>When UIPath came back for real, the obvious move was to switch Scribe off. I didn’t. The outage had made the actual risk legible: a single write-back, on <em>any</em> platform, operated by a team that doesn’t watch it after hours, is a single point of failure with a multi-day blast radius.</p>

<p>So we kept Scribe running and added layers. Today the stack looks like this:</p>

<ol>
  <li>
    <p><strong>Independent detection (predates Scribe).</strong> The confirmation service verifies every finalized change against the schedule re-imported from the source system. It never trusts the writer’s self-report — it checks the system of record. This is ground truth, and it’s the backstop under everything else.</p>
  </li>
  <li>
    <p><strong>Automatic takeover when the bot stalls.</strong> If a job sits in UIPath’s queue in the <code class="language-plaintext highlighter-rouge">NEW</code> stage for more than 20 minutes, we delete it from their queue and take it over ourselves. This is the direct answer to “nobody monitors it after hours.” An unattended failure on their side can no longer quietly become a problem for our users on ours — a stalled job now has a deadline, and when it blows it, we just do the work.</p>
  </li>
  <li>
    <p><strong>Scribe as stand-in.</strong> When UIPath is unavailable wholesale, a routing switch sends everything to Scribe. That’s what carried us through the outage week, and it’s still there.</p>
  </li>
  <li>
    <p><strong>Scribe as botsitter.</strong> In normal operation UIPath does the primary write, and Scribe independently re-reads the live schedule afterward, diffs it against what our system intended, and remediates discrepancies on the spot. UIPath does the work; Scribe checks the work and fixes what’s wrong — immediately, without waiting for a human to notice.</p>
  </li>
  <li>
    <p><strong>The eventual API.</strong> The vendor’s real API, when it lands, becomes the primary path — and every layer above stays exactly where it is.</p>
  </li>
</ol>

<p>Read those together and the shape is: we detect independently, we take over automatically, we verify everything a bot claims it did, and we confirm the round trip from the source of truth regardless of who wrote it. Any single layer failing no longer means schedule drift for a nurse who needs to know when they’re working.</p>

<h3 id="was-keeping-it-on-worth-it">Was keeping it on worth it?</h3>

<p>Our botsitter keeps running now that UIPath is stable. Take a representative week, five weeks after the outage ended. Scribe ran <strong>395 sessions</strong>. 389 of them executed, and 345 of those were adjudicating an actual UIPath write — the other 44 were confirmations of schedule imports from elsewhere, which Scribe checks but UIPath never touched. Of the 345 that were grading UIPath’s homework:</p>

<ul>
  <li><strong>284 (82.3%) came back <code class="language-plaintext highlighter-rouge">verified</code></strong> — UIPath did the work correctly, Scribe confirmed it, nothing to do.</li>
  <li><strong>61 (17.7%) came back <code class="language-plaintext highlighter-rouge">remediated</code></strong> — Scribe found a discrepancy between what we intended and what the source system actually showed, and fixed it on the spot.</li>
</ul>

<p><strong>More than one in every six automated writes still needs correcting.</strong> Not during a crisis — during a totally normal week, on a healthy platform. Those aren’t outages; they’re the quiet stuff. An edit that silently didn’t commit. A shift that didn’t have a note written in explaining what changed. Each one is a nurse whose schedule says something different from what they agreed to, and none of them would have thrown an error. Previously we would have manually fixed these ourselves, but now we have a system that can catch these issues automatically and fix them in real-time.</p>

<p>The stuck-job takeover fired <strong>7 times</strong> that week, too. Seven jobs that sat in the queue past 20 minutes and would otherwise have waited for someone to notice.</p>

<p>That’s my answer on whether to turn it off. The outage was never the real problem — it was just the loud version of a quiet one we’d been living with.</p>

<p>The outage was the forcing function. The defense-in-depth is what we kept.</p>

<hr />

<h2 id="the-technical-deep-dive">The technical deep-dive</h2>

<p>For the engineers. Here’s roughly how it’s built.</p>

<p><strong>Scribe drives the real UI, in Python.</strong> It uses Playwright (headless Chromium, baked into our worker image) to log into the source scheduling system and perform the same actions a human scheduler would: navigate to a date, open a unit grid, add a shift, edit a shift’s time or notes, read a row back to confirm. There’s no secret API — it’s the same surface UIPath automated, which is exactly why Scribe could stand in one-to-one.</p>

<p><strong>Everything is a verify-and-remediate session.</strong> The core object is a <code class="language-plaintext highlighter-rouge">ScribeSession</code> with an explicit lifecycle: <code class="language-plaintext highlighter-rouge">pending → processing → remediation_planned → remediating → remediated</code>, plus terminal states like <code class="language-plaintext highlighter-rouge">needs_review</code> and <code class="language-plaintext highlighter-rouge">remediation_failed</code>. A session spawns per parent request (a swap, a coverage, a schedule change) once that parent’s sibling tasks have <em>settled</em>. It re-scans the live grid, diffs against intended state, and either confirms a match or plans concrete adjustments.</p>

<p><strong>“Settled” includes “stuck.”</strong> This is the takeover layer in code. A task settles normally when it reaches a terminal status (<code class="language-plaintext highlighter-rouge">COMPLETE</code>/<code class="language-plaintext highlighter-rouge">ERROR</code>/<code class="language-plaintext highlighter-rouge">ABANDONED</code>/<code class="language-plaintext highlighter-rouge">BYPASSED</code>). But a task sitting in <code class="language-plaintext highlighter-rouge">NEW</code> — accepted into UIPath’s queue and never picked up — blocks only until a staleness threshold passes (<code class="language-plaintext highlighter-rouge">SCRIBE_STUCK_NEW_THRESHOLD_MINUTES</code>, currently 20). Past that, we cancel the queue item and dispatch a session with <code class="language-plaintext highlighter-rouge">trigger_reason=STUCK_NEW</code>. A bot that never starts is now just another settled state, and the pipeline keeps moving.</p>

<p><strong>Routing is a single switch.</strong> An <code class="language-plaintext highlighter-rouge">RPA_BYPASS_UIPATH</code> path resubmits a parent’s tasks so they flow to Scribe instead of the UIPath queue, canceling any live queue item first. During the outage that switch was “everything routes to Scribe.” It’s the scriptable, auditable equivalent of an admin “resubmit these tasks” action — which is what let us flip back on June 10, discover UIPath still wasn’t right, and flip off again within the hour without a deploy.</p>

<p><strong>The confirmation service is deliberately separate.</strong> It lives in a different app (<code class="language-plaintext highlighter-rouge">schedule_requests</code>), predates Scribe by months, and shares no code path with the writers. It reconciles finalized requests against the re-imported schedule, with escalating thresholds — a standard alert window, a tighter one for shifts starting within hours — and an <code class="language-plaintext highlighter-rouge">ImportHealth</code> gate so ordinary import lag doesn’t fire false alarms. Keeping it independent is the whole point: if it shared plumbing with the thing it’s checking, it would fail in the same ways at the same time.</p>

<p><strong>The hard part was the web UI, not the logic.</strong> A freshly-changed grid hydrates asynchronously — a new cell can render a beat <em>after</em> the loader clears — so a naive scan misses it and wrongly concludes the write failed. Scribe does bounded re-scans before deciding a cell is absent. Add-confirmation is date-pinned to the target day-column to avoid opening every same-time panel on a busy unit. Saves are confirmed by toast <em>and</em> by re-reading the new row’s id. Edits set the time field last, after the panel hydrates, because setting it early gets clobbered. Transient iframe detaches self-heal. Every one of those lines exists because of a specific way the UI lied to us — and that long tail is exactly the work AI made survivable in a weekend.</p>

<p><strong>Concurrency is guarded.</strong> A database constraint enforces at most one open session per parent request. Stale workers that try to update a session already reaped or replaced hit a <code class="language-plaintext highlighter-rouge">Superseded</code> guard and back off instead of racing. Status transitions are filtered updates (<code class="language-plaintext highlighter-rouge">update where status = expected</code>), so two workers can’t both advance the same session. Sessions run on a dedicated Celery worker so a wedged browser can’t starve the rest of the queue.</p>

<p><strong>It fails loud, not silent.</strong> A scheduled reaper sweeps sessions stuck past a threshold — re-dispatching stale pending rows, retrying stale active ones below the attempt cap, hard-failing the rest. An attention monitor emails our internal team — deliberately <em>not</em> the customer’s RPA address — whenever a session needs human eyes. The whole point of a botsitter is that it never fails quietly. A discrepancy it can’t fix becomes an alert, not a surprise a week later.</p>

<hr />

<h2 id="the-takeaway">The takeaway</h2>

<p>The headline is that Claude and Codex let one engineer ship a production RPA replacement over a weekend. That’s true, and it’s remarkable.</p>

<p>But the lesson I keep coming back to is quieter, and airplanes got there first. A 777’s flight controls don’t just carry three computers — they carry three channels, each running three lanes on different processors, compiled by different compilers, isolated physically and electrically. The redundancy isn’t the count; it’s the dissimilarity. Three identical computers don’t vote, they agree — confidently, simultaneously, right up until they’re all wrong together.</p>

<p>That’s what the confirmation service is, and why it’s the least exciting code we own. It isn’t a third opinion about whether the write landed — it’s the only thing in the room that isn’t an opinion. UIPath’s report and Scribe’s report both rest on the same assumption: that the system doing the work can tell you whether the work happened. The confirmation service doesn’t ask. It goes and looks. When every voter can be wrong the same way, the tie-breaker can’t be another vote — it has to be the one that reads reality instead of reporting on itself. It’s the reason we weren’t blind while the people responsible for the failure were afk.</p>

<p>AI gave us the speed to replace a dependency in 48 hours. But speed only helps if you already know something is broken. Build the thing that tells you the truth first.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Our customer's RPA platform failed silently for five and a half days — 592 tasks, zero errors reported. How we knew anyway, and what I built over the weekend to replace it.]]></summary></entry><entry><title type="html">Why Google threw out FP and FCP in favor of LCP (Correlation study)</title><link href="https://davidsommers.com/2021/05/25/why-google-threw-out-fp-and-fcp.html" rel="alternate" type="text/html" title="Why Google threw out FP and FCP in favor of LCP (Correlation study)" /><published>2021-05-25T00:00:00-04:00</published><updated>2021-05-25T00:00:00-04:00</updated><id>https://davidsommers.com/2021/05/25/why-google-threw-out-fp-and-fcp</id><content type="html" xml:base="https://davidsommers.com/2021/05/25/why-google-threw-out-fp-and-fcp.html"><![CDATA[<p>I wrote an article for the RentPath company blog on <a href="https://blog.rentpathcode.com/why-google-threw-out-fp-and-fcp-in-favor-of-lcp-correlation-study-d7fdccd0c634?sk=6065303fd9e6bbcd5f04c08b3555f30e">a correlation study into different web metrics</a>.</p>]]></content><author><name></name></author></entry><entry><title type="html">Stop building websites for iPhones</title><link href="https://davidsommers.com/2021/05/07/stop-building-websites-for-iphones.html" rel="alternate" type="text/html" title="Stop building websites for iPhones" /><published>2021-05-07T00:00:00-04:00</published><updated>2021-05-07T00:00:00-04:00</updated><id>https://davidsommers.com/2021/05/07/stop-building-websites-for-iphones</id><content type="html" xml:base="https://davidsommers.com/2021/05/07/stop-building-websites-for-iphones.html"><![CDATA[<p>I wrote an article for the RentPath company blog on <a href="https://blog.rentpathcode.com/analyzing-performance-e7aed196df64">mobile performance with slow web sites</a>.</p>]]></content><author><name></name></author></entry><entry><title type="html">Splitting out your marketing pages from your main web application</title><link href="https://davidsommers.com/2016/10/22/splitting-out-your-marketing-pages-from-your-main-web-app.html" rel="alternate" type="text/html" title="Splitting out your marketing pages from your main web application" /><published>2016-10-22T00:00:00-04:00</published><updated>2016-10-22T00:00:00-04:00</updated><id>https://davidsommers.com/2016/10/22/splitting-out-your-marketing-pages-from-your-main-web-app</id><content type="html" xml:base="https://davidsommers.com/2016/10/22/splitting-out-your-marketing-pages-from-your-main-web-app.html"><![CDATA[<p>I read an article last month on
<a href="https://christoph.luppri.ch/articles/2016/09/25/adding-static-pages-to-your-rails-app/">Adding static pages to your Rails app</a>
and I’ve been meaning to write up how we do the same thing at
<a href="https://www.clockwisemd.com">Clockwise.MD</a>. In fact, it’s been a very long time
since I’ve written on my blog.</p>

<p>It’s funny that I’m finding time to write in the middle of a hackathon at
<a href="https://atlanta.48in48.org/">48-in-48</a>. After joining three teams, it seems I’ve
completed everything on my list and I’m waiting on Stephanie to wrap up. So that
must mean it’s a great time to start writing again and create a blog entry!
Hopefully I can write up something about this experience when I’m done.</p>

<p>Back to static pages for your web application.</p>

<p>The benefit of splitting out your main app from your static marketing pages is
creating an enviornment the best suits your authors/editors, designers and engineers.
Take Clockwise.MD for instance, we have a well defined SDLC process for making
changes to the web application. It includes testing, peer review, approvals and
carefully selecting which memoral quote, interesting fact or animated GIF to add
to your PR approval. The flip side is our head of marketing shouldn’t need an
extensive PR process to add a new <a href="https://www.clockwisemd.com/about">pet to our Team page</a>.</p>

<p>Similar to the earlier article I mentioned, we needed to load static pages up
for our app but we don’t want to host those files on our actual web server.
Instead, we created our web site using <a href="https://jekyllrb.com/">Jekyll</a>. The
resulting static site is uploaded to S3 with a CDN in front to make the site
load fast. Then any files we don’t want to serve up from our web application,
we proxy down from our CDN and send it to the requestor. We also cache it so
that it’s super fast.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">PagesController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
  <span class="k">def</span> <span class="nf">www_base</span>
    <span class="n">expires_in</span> <span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">,</span> <span class="ss">public: </span><span class="kp">true</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">s3_pages</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="nf">path</span><span class="p">)</span>
    <span class="n">render</span> <span class="ss">status: </span><span class="n">response</span><span class="p">.</span><span class="nf">code</span><span class="p">,</span> <span class="ss">inline: </span><span class="n">response</span><span class="p">.</span><span class="nf">body</span>
  <span class="k">end</span>

  <span class="kp">private</span>

  <span class="k">def</span> <span class="nf">s3_pages</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
    <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="p">(</span><span class="s1">'https://your-s3-or-cdn-site.com'</span> <span class="o">+</span> <span class="n">adjusted_path</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
    <span class="n">http</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">host</span><span class="p">,</span> <span class="n">uri</span><span class="p">.</span><span class="nf">port</span><span class="p">)</span>
    <span class="n">http</span><span class="p">.</span><span class="nf">use_ssl</span> <span class="o">=</span> <span class="kp">true</span>
    <span class="n">http</span><span class="p">.</span><span class="nf">ciphers</span> <span class="o">=</span> <span class="s1">'TLSv1.2:!aNULL:!eNULL'</span>
    <span class="n">http</span><span class="p">.</span><span class="nf">ssl_version</span> <span class="o">=</span> <span class="ss">:TLSv1_2</span>
    <span class="n">http</span><span class="p">.</span><span class="nf">verify_mode</span> <span class="o">=</span> <span class="no">OpenSSL</span><span class="o">::</span><span class="no">SSL</span><span class="o">::</span><span class="no">VERIFY_PEER</span>
    <span class="n">request</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">::</span><span class="no">Get</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">request_uri</span><span class="p">)</span>
    <span class="n">http</span><span class="p">.</span><span class="nf">request</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Then at the bottom of your <code class="language-plaintext highlighter-rouge">routes.rb</code> file, add:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c1"># external pages</span>
  <span class="n">root</span> <span class="ss">to: </span><span class="s1">'pages#www_base'</span>
  <span class="n">get</span> <span class="s1">'*unmatched_route'</span><span class="p">,</span> <span class="ss">to: </span><span class="s1">'pages#www_base'</span>
</code></pre></div></div>

<p>Now your home page will hit <code class="language-plaintext highlighter-rouge">pages#www_base</code>. Additionally, any page that’s
not already specified earlier on your web application’s routes will be routed
to your static site. If a user hits <code class="language-plaintext highlighter-rouge">/products</code>, then that same request will
get served up from your static site. If a user hits <code class="language-plaintext highlighter-rouge">/foo/bar</code> and that path
doesn’t exist, then they’ll even get your static site’s 404 page and <code class="language-plaintext highlighter-rouge">www_base</code>
will send back the correct 404 status code as well.</p>

<p>To make things easier for your marketing folks, let them edit pages using
Github’s online editors then setup a CI process to rebuild and redeploy the
static site on every commit. Instant updates without having to the install
the Wordpress malware.</p>]]></content><author><name></name></author></entry><entry><title type="html">Docker is great, slow Rails apps is not</title><link href="https://davidsommers.com/2012/12/08/docker-slow-rails-app.html" rel="alternate" type="text/html" title="Docker is great, slow Rails apps is not" /><published>2012-12-08T00:00:00-05:00</published><updated>2012-12-08T00:00:00-05:00</updated><id>https://davidsommers.com/2012/12/08/docker-slow-rails-app</id><content type="html" xml:base="https://davidsommers.com/2012/12/08/docker-slow-rails-app.html"><![CDATA[<p>It’s <a href="http://oliverguenther.de/2015/05/docker-containers-for-development/">apparently a known issue</a>
to have slow Rails apps under Docker in development due to folder sharing in VirtualBox.</p>

<p>There are many suggestions as to how to fix this, so I tried a few.</p>

<ul>
  <li><a href="http://oliverguenther.de/2015/05/docker-host-volume-synchronization/">Sharing Host Volumes with Docker Containers</a> is a good overview of multiple things to try by Oliver Günther.</li>
  <li>I started with <a href="https://github.com/brikis98/docker-osx-dev">docker-osx-dev</a> but received rsync errors after running it for a few minutes.</li>
  <li>Switched to VMWare Fusion and that didn’t help so maybe it’s not VirtualBox file sharing.</li>
  <li>Tried <a href="https://github.com/synack/docker-rsync">docker-rysnc</a> under Fusion but it gave me:</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync: connection unexpectedly closed <span class="o">(</span>0 bytes received so far<span class="o">)</span> <span class="o">[</span>sender]
rsync error: error <span class="k">in </span>rsync protocol data stream <span class="o">(</span>code 12<span class="o">)</span> at /BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-47/rsync/io.c<span class="o">(</span>453<span class="o">)</span> <span class="o">[</span><span class="nv">sender</span><span class="o">=</span>2.6.9]
error: <span class="nb">exit </span>status 12
Watching <span class="k">for </span>file changes ...
</code></pre></div></div>

<p>Maybe this magic bullet shit won’t work, let’s try using an image as part of compose so that we’re more explicit about this syncing process.</p>

<ul>
  <li>Looks like <a href="https://github.com/leighmcculloch/docker-unison">docker-unison</a> works to sync files and it syncs them quickly but the application still takes 20 seconds to load a simple page.</li>
  <li><a href="https://github.com/codekitchen/dinghy">dinghy</a> created a VM as a wrapper to docker-machine and no luck with that too.</li>
</ul>

<p>Other NFS scripting options:</p>

<ul>
  <li><a href="http://syskall.com/using-boot2docker-using-nfs-instead-of-vboxsf/">Boot2docker: Using nfs instead of vboxsf to mount /Users</a></li>
  <li><a href="http://blog.blackfire.io/how-we-use-docker.html">How Blackfire leverages Docker</a> under the File Sharing section</li>
  <li><a href="https://gist.github.com/mattes/4d7f435d759ca2581347">boot2docker-nfs.rb</a> is a simple gist working with VirtualBox</li>
</ul>

<p>Since I have Fusion installed though, this seems like a lot of work to just get a basic Rails app to run.</p>

<p>My solution:</p>

<ul>
  <li>Use Docker for everything not Rails or Guard. Postgres and workers seem fast enough.</li>
  <li>Run Rails locally with your standard <code class="language-plaintext highlighter-rouge">gem install bundler; bundle</code>.</li>
  <li>Leave the app running in Docker Compose if I need to check out anything in Linux but avoid it at all costs.</li>
</ul>

<p>I’ve got too much real work to do.</p>]]></content><author><name></name></author></entry><entry><title type="html">Upgrading to Mountain Lion</title><link href="https://davidsommers.com/2012/10/15/upgrading-to-mountain-lion.html" rel="alternate" type="text/html" title="Upgrading to Mountain Lion" /><published>2012-10-15T00:00:00-04:00</published><updated>2012-10-15T00:00:00-04:00</updated><id>https://davidsommers.com/2012/10/15/upgrading-to-mountain-lion</id><content type="html" xml:base="https://davidsommers.com/2012/10/15/upgrading-to-mountain-lion.html"><![CDATA[<p>So I finally got around to upgrading to Mountain Lion.</p>

<h4 id="required-steps">Required Steps</h4>

<p>My first stop was to follow Thoughtbot’s
<a href="http://robots.thoughtbot.com/post/27985816073/the-hitchhikers-guide-to-riding-a-mountain-lion">The Hitchhiker’s Guide to Riding a Mountain Lion</a>.</p>

<ul>
  <li>Get Xcode and Command Line Tools installed</li>
  <li>Fix homebrew permissions with <code class="language-plaintext highlighter-rouge">sudo chown -R $USER /usr/local</code></li>
  <li>Update homebrew with <code class="language-plaintext highlighter-rouge">brew update</code></li>
  <li>Install gcc 4.2 to install older rubies with <code class="language-plaintext highlighter-rouge">brew tap homebrew/dupes;
brew install autoconf automake apple-gcc42</code></li>
  <li>Install X11 support with <a href="http://xquartz.macosforge.org/landing">XQuartz</a></li>
</ul>

<h4 id="tldr-for-installing-ruby-187">TL;DR for installing Ruby 1.8.7</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export CPPFLAGS=-I/opt/X11/include
$ CC=/usr/local/bin/gcc-4.2 rvm install 1.8.7
</code></pre></div></div>

<h4 id="my-little-story">My little story</h4>

<p>Initially I didn’t want to install X11 since I didn’t think I needed it.</p>

<p>Then I tried to use <code class="language-plaintext highlighter-rouge">vim</code> (not MacVim, I just wanted to edit a file
quickly) and received:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ vim
Vim: Caught deadly signal SEGV
Vim: Finished.
[1]    42573 segmentation fault  vim
</code></pre></div></div>

<p>What version of vim?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ vim --version
VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Jun 20 2012 13:16:02)
Compiled by root@apple.com
/snip/
</code></pre></div></div>

<p>It seems that if you <code class="language-plaintext highlighter-rouge">brew install vim</code>, it still breaks:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ /usr/local/bin/vim --version
VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Oct 15 2012 18:02:59)
MacOS X (unix) version
Included patches: 1-687
Compiled by databyte@nyx
</code></pre></div></div>

<p>Turns out that I need ruby-1.8.7 due to a compatibility in
<a href="https://github.com/carlhuda/janus">Janus</a>, which was outdated. The
quick fix was upgrading Janus by running <code class="language-plaintext highlighter-rouge">rake</code> within <code class="language-plaintext highlighter-rouge">~/.vim</code> but I
might as well compile ruby-1.8.7 since I bothered to install gcc 4.2.</p>

<p>Let’s try to install ruby-1.8.7 with rvm:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rvm install ruby-1.8.7-p370
No binary rubies available for: osx/10.8/x86_64/ruby-1.8.7-p370.
Continuing with compilation. Please read 'rvm mount' to get more information on binary rubies.
Installing Ruby from source to: /Users/databyte/.rvm/rubies/ruby-1.8.7-p370, this may take a while depending on your cpu(s)...
ruby-1.8.7-p370 - #downloading ruby-1.8.7-p370, this may take a while depending on your connection...
ruby-1.8.7-p370 - #extracting ruby-1.8.7-p370 to /Users/databyte/.rvm/src/ruby-1.8.7-p370
ruby-1.8.7-p370 - #extracted to /Users/databyte/.rvm/src/ruby-1.8.7-p370
Applying patch /Users/databyte/.rvm/patches/ruby/1.8.7/stdout-rouge-fix.patch
Applying patch /Users/databyte/.rvm/patches/ruby/1.8.7/no_sslv2.diff
ruby-1.8.7-p370 - #configuring
ruby-1.8.7-p370 - #compiling
Error running 'make', please read /Users/databyte/.rvm/log/ruby-1.8.7-p370/make.log
There has been an error while running make. Halting the installation.
Please be aware that you just installed a ruby that requires        2 patches just to be compiled on up to date linux system.
This may have known and unaccounted for security vulnerabilities.
Please consider upgrading to Ruby 1.9.3-286 which will have all of the latest security patches.
</code></pre></div></div>

<p>Turns out in the logs that:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/include/tk.h:78:23: error: X11/Xlib.h: No such file or directory
</code></pre></div></div>

<p>Hence the need for XQuartz. Now let’s install XQuartz and then reinstall ruby
but also explicity set X11’s include path:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export CPPFLAGS=-I/opt/X11/include
$ CC=/usr/local/bin/gcc-4.2 rvm reinstall 1.8.7
Removing /Users/databyte/.rvm/src/ruby-1.8.7-p370...
/Users/databyte/.rvm/rubies/ruby-1.8.7-p370 has already been removed.
No binary rubies available for: osx/10.8/x86_64/ruby-1.8.7-p370.
Continuing with compilation. Please read 'rvm mount' to get more information on binary rubies.
Installing Ruby from source to: /Users/databyte/.rvm/rubies/ruby-1.8.7-p370, this may take a while depending on your cpu(s)...
ruby-1.8.7-p370 - #downloading ruby-1.8.7-p370, this may take a while depending on your connection...
ruby-1.8.7-p370 - #extracting ruby-1.8.7-p370 to /Users/databyte/.rvm/src/ruby-1.8.7-p370
ruby-1.8.7-p370 - #extracted to /Users/databyte/.rvm/src/ruby-1.8.7-p370
Applying patch /Users/databyte/.rvm/patches/ruby/1.8.7/stdout-rouge-fix.patch
Applying patch /Users/databyte/.rvm/patches/ruby/1.8.7/no_sslv2.diff
ruby-1.8.7-p370 - #configuring
ruby-1.8.7-p370 - #compiling
ruby-1.8.7-p370 - #installing
Removing old Rubygems files...
Installing rubygems-1.8.24 for ruby-1.8.7-p370 ...
Installation of rubygems completed successfully.
Saving wrappers to '/Users/databyte/.rvm/bin'.
ruby-1.8.7-p370 - #adjusting #shebangs for (gem irb erb ri rdoc testrb rake).
ruby-1.8.7-p370 - #importing default gemsets (/Users/databyte/.rvm/gemsets/)
Install of ruby-1.8.7-p370 - #complete
Please be aware that you just installed a ruby that requires        2 patches just to be compiled on up to date linux system.
This may have known and unaccounted for security vulnerabilities.
Please consider upgrading to Ruby 1.9.3-286 which will have all of the latest security patches.
Making gemset ruby-1.8.7-p370 pristine.
Making gemset ruby-1.8.7-p370@global pristine.
</code></pre></div></div>

<p>Well that works and now I have ruby 1.8.7. Let’s try out vim again:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rvm use 1.8.7
$ /usr/local/bin/vim
</code></pre></div></div>

<p>Yep, that works too!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ vim --version
VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Oct 15 2012 18:08:38)
MacOS X (unix) version
Included patches: 1-687
Compiled by databyte@
Huge version without GUI.  Features included (+) or not (-):
+arabic +autocmd -balloon_eval -browse ++builtin_terms +byte_offset +cindent
-clientserver +clipboard +cmdline_compl +cmdline_hist +cmdline_info +comments
+conceal +cryptv +cscope +cursorbind +cursorshape +dialog_con +diff +digraphs
-dnd -ebcdic +emacs_tags +eval +ex_extra +extra_search +farsi +file_in_path
+find_in_path +float +folding -footer +fork() -gettext -hangul_input +iconv
+insert_expand +jumplist +keymap +langmap +libcall +linebreak +lispindent
+listcmds +localmap -lua +menu +mksession +modify_fname +mouse -mouseshape
+mouse_dec -mouse_gpm -mouse_jsbterm +mouse_netterm -mouse_sysmouse
+mouse_xterm +mouse_urxvt +mouse_sgr +multi_byte +multi_lang -mzscheme
+netbeans_intg +path_extra -perl +persistent_undo +postscript +printer +profile
 +python -python3 +quickfix +reltime +rightleft +ruby +scrollbind +signs
+smartindent -sniff +startuptime +statusline -sun_workshop +syntax +tag_binary
+tag_old_static -tag_any_white -tcl +terminfo +termresponse +textobjects +title
 -toolbar +user_commands +vertsplit +virtualedit +visual +visualextra +viminfo
+vreplace +wildignore +wildmenu +windows +writebackup -X11 -xfontset -xim -xsmp
 -xterm_clipboard -xterm_save
   system vimrc file: "$VIM/vimrc"
     user vimrc file: "$HOME/.vimrc"
      user exrc file: "$HOME/.exrc"
  fall-back for $VIM: "/usr/local/share/vim"
Compilation: cc -c -I. -Iproto -DHAVE_CONFIG_H   -DMACOS_X_UNIX -no-cpp-precomp  -g -O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
Linking: cc   -L.    -L/usr/local/lib -o vim       -lm  -lncurses -liconv -framework Cocoa     -framework Python   -lruby -lobjc
</code></pre></div></div>]]></content><author><name></name></author></entry><entry><title type="html">Help Club</title><link href="https://davidsommers.com/2012/08/06/help-club.html" rel="alternate" type="text/html" title="Help Club" /><published>2012-08-06T00:00:00-04:00</published><updated>2012-08-06T00:00:00-04:00</updated><id>https://davidsommers.com/2012/08/06/help-club</id><content type="html" xml:base="https://davidsommers.com/2012/08/06/help-club.html"><![CDATA[<p>We are all craftsman and we all learn something new every week if not
every day. We learn through the process of having a problem then
solving it and sometimes we need help.</p>

<p>In New York, I’ve attended and helped many developers in the local
<a href="http://www.meetup.com/nyc-on-rails/">NYC on Rails</a> meetups through a
weekly meeting called Hacker Hours. It’s a nice change of pace from the
normal Ruby meetups because Hacker Hours attracts a lot of non-Ruby
developers in a wide range of skill ranges over a wider diverse set of
people. I helped in Ruby, PHP, .NET, Java, HTML/CSS/JavaScript, iPhone, etc.</p>

<p>I have plans in motion to migrate back to Atlanta and once I settle in,
I plan to create the Help Club.</p>

<p>The rules are simple:</p>

<ol>
  <li>You do talk about Help Club.</li>
  <li>You DO tweet about Help Club.</li>
  <li>You need to show up to learn or to help.</li>
</ol>

<p>Based on rule #3, the meeting will lack suites and recruiters. This is
for engineers looking to accelerate their learning or mentoring skills
with complete comfort.</p>

<p>The process of helping or being helped is encouraged and shared through
a ranking process. For every activity you participate in, you will earn
points.</p>

<ul>
  <li>1 pt per meeting when learning.</li>
  <li>3 pts per meeting when teaching.</li>
  <li>10 pts for a lightning talk.</li>
  <li>25 pts for a short presentation.</li>
</ul>

<p>Those points go towards a rank.</p>

<ul>
  <li>10 pts White Belt</li>
  <li>50 pts Yellow Belt</li>
  <li>100 pts Green Belt</li>
  <li>250 pts Brown Belt</li>
  <li>500 pts Black Belt</li>
</ul>

<p>I’ll be the idiot grinning and cheerleading everyone as I hand out
belts.</p>

<p>My plan is to have a bi-weekly 2.5 hour meeting. We will have two 1 hour
helping sessions with a 30 minute presentation block in the middle. The
presentations will either be a single short presentation or three
lightning talks.</p>

<p>I look forward to working on this idea with my future coworkers and in
encouraging everyone to learn. From beginners to experts. From desktop
applications to mobile and web sites. I’ll even bring in Arduino and
Raspberry Pi devices if there are interested developers.</p>

<p>Suggestions welcomed and I hope to announce something soon!</p>]]></content><author><name></name></author></entry><entry><title type="html">Heroku the Magician!</title><link href="https://davidsommers.com/2012/08/02/heroku-the-magician.html" rel="alternate" type="text/html" title="Heroku the Magician!" /><published>2012-08-02T00:00:00-04:00</published><updated>2012-08-02T00:00:00-04:00</updated><id>https://davidsommers.com/2012/08/02/heroku-the-magician</id><content type="html" xml:base="https://davidsommers.com/2012/08/02/heroku-the-magician.html"><![CDATA[<p>In an earlier blog post entitled <a href="/2012/07/27/testing-in-isolation-example-with-rabl.html">Heroku’s uptime numbers are off</a>,
Heroku claimed their uptime was 99.28% and I calculated it closer to 96.25%.</p>

<p>Status page a few days ago:
<img src="http://f.cl.ly/items/1X2S412f1w2t3D0M1810/Heroku%20Status.png" alt="Heroku Status in July" /></p>

<p>Now, they’re combining multiple months together to decrease the number.</p>

<p>Status page today:
<img src="http://f.cl.ly/items/3M0Y1B3S352z091l2f1k/Heroku%20New%20June.png" alt="Heroku new Status for June" /></p>

<p>The numbers are looking better and better eh?</p>

<p>Three questions:</p>

<ol>
  <li>Why are they listing July separate (which was a good month right?)
and then the 3 previous months with July included again?</li>
  <li>What happened to January through April?</li>
  <li>Is this going to be a rolling previous three months? Or is next month
going to drop May and June then list only July and August?</li>
</ol>

<p>I wish their marketing department didn’t make these pages.</p>

<p>They should list percentages for the current month and the previous
month with a link to a page listing all the months ever recorded.</p>

<p>Even better would be to mimic your <a href="http://status.salesforce.com/trust/status/">parent company (Salesforce)</a>
or <a href="http://status.aws.amazon.com/">Amazon</a>. The way they list history is
much easier to digest.</p>

<p>Amazon:
<img src="http://f.cl.ly/items/103E3T05450T2p2C0w2I/AWS%20Service%20Health%20Dashboard%20-%20Aug%201,%202012.png" alt="Amazon Status" /></p>

<p>Salesforce:
<img src="http://f.cl.ly/items/0z3O0r0A3d0c3w2D3T3L/Trust.salesforce.com%20-%20System%20Status%20-%20salesforce.com.png" alt="Salesforce Status" /></p>]]></content><author><name></name></author></entry><entry><title type="html">Don’t confuse popular or easiest with best</title><link href="https://davidsommers.com/2012/08/01/dont-confuse-popular-or-easiest-with-best.html" rel="alternate" type="text/html" title="Don’t confuse popular or easiest with best" /><published>2012-08-01T00:00:00-04:00</published><updated>2012-08-01T00:00:00-04:00</updated><id>https://davidsommers.com/2012/08/01/dont-confuse-popular-or-easiest-with-best</id><content type="html" xml:base="https://davidsommers.com/2012/08/01/dont-confuse-popular-or-easiest-with-best.html"><![CDATA[<p>I recently bumped into someone who proclaimed PHP as the greatest
language for web development and he had zero desire to learn another
framework, especially not Ruby on Rails.</p>

<p>I, for one, couldn’t care less if you used Ruby or Rails, Python or
Django, C# or ASP.NET. I do care that you would a) only stick with what
you know and b) choose PHP.</p>

<p>For the former, it’s simple - you should assume you simply don’t know
everything there is to know and continually try to better yourself. If
you haven’t reached that conclusion yet, the book
<a href="http://pragprog.com/book/cfcar2/the-passionate-programmer">The Passionate Programmer</a>
will surely help.</p>

<p>For the latter, hmm, PHP eh?</p>

<p>I’ve done PHP development as recently as last year but to read into more
of a PHP’ers point of view, I recently read a blog post named
<a href="http://fabien.potencier.org/article/64/php-is-much-better-than-what-you-think">PHP is much better than what you think</a>.</p>

<p>In it, the author, Fabien Potencier, sounds just like the person I
mentioned earlier. The opening paragraphs calls PHP the easiest, most
popular and quickest language. All great reasons to pick one language
over another. So let’s break this down.</p>

<h4 id="the-language">The Language</h4>

<p>PHP is old but instead of a language that moves forward with its syntax,
PHP chooses to build on top of the old cruft and never clears away the
cobwebs. Its syntax coherence is below most other languages. A blog titled
<a href="http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/">PHP: a fractal of bad design</a>
says it better than anyone else:</p>

<blockquote>
  <ul>
    <li>PHP is full of surprises: <code class="language-plaintext highlighter-rouge">mysql_real_escape_string</code>, <code class="language-plaintext highlighter-rouge">E_ALL</code></li>
    <li>PHP is inconsistent: <code class="language-plaintext highlighter-rouge">strpos</code>, <code class="language-plaintext highlighter-rouge">str_rot13</code></li>
    <li>PHP requires boilerplate: error-checking around C API calls, <code class="language-plaintext highlighter-rouge">===</code></li>
    <li>PHP is flaky: <code class="language-plaintext highlighter-rouge">==</code>, <code class="language-plaintext highlighter-rouge">foreach ($foo as &amp;$bar)</code></li>
    <li>PHP is opaque: no stack traces by default or for fatals, complex error reporting</li>
  </ul>
</blockquote>

<p>Consistency is the key to discovering a new language, framework or API.
One of the key arguments for PHP is that it’s easy. I don’t see how
inconsistency is easy. From the same article:</p>

<blockquote>
  <ul>
    <li>Underscore versus not: <code class="language-plaintext highlighter-rouge">strpos</code>/<code class="language-plaintext highlighter-rouge">str_rot13</code>, <code class="language-plaintext highlighter-rouge">php_uname</code>/<code class="language-plaintext highlighter-rouge">phpversion</code>, <code class="language-plaintext highlighter-rouge">base64_encode</code>/<code class="language-plaintext highlighter-rouge">urlencode</code>, <code class="language-plaintext highlighter-rouge">gettype</code>/<code class="language-plaintext highlighter-rouge">get_class</code></li>
    <li>“to” versus 2: <code class="language-plaintext highlighter-rouge">ascii2ebcdic</code>, <code class="language-plaintext highlighter-rouge">bin2hex</code>, <code class="language-plaintext highlighter-rouge">deg2rad</code>, <code class="language-plaintext highlighter-rouge">strtolower</code>, <code class="language-plaintext highlighter-rouge">strtotime</code></li>
    <li>Object+verb versus verb+object: <code class="language-plaintext highlighter-rouge">base64_decode</code>, <code class="language-plaintext highlighter-rouge">str_shuffle</code>, <code class="language-plaintext highlighter-rouge">var_dump</code> versus <code class="language-plaintext highlighter-rouge">create_function</code>, <code class="language-plaintext highlighter-rouge">recode_string</code></li>
    <li>Argument order: <code class="language-plaintext highlighter-rouge">array_filter($input, $callback)</code> versus <code class="language-plaintext highlighter-rouge">array_map($callback, $input)</code>, <code class="language-plaintext highlighter-rouge">strpos($haystack, $needle)</code> versus <code class="language-plaintext highlighter-rouge">array_search($needle, $haystack)</code></li>
    <li>Prefix confusion: <code class="language-plaintext highlighter-rouge">usleep</code> versus <code class="language-plaintext highlighter-rouge">microtime</code></li>
    <li>Case insensitive functions vary on where the <code class="language-plaintext highlighter-rouge">i</code> goes in the name.</li>
    <li>About half the array functions actually start with <code class="language-plaintext highlighter-rouge">array_</code>. The others do not.</li>
    <li><code class="language-plaintext highlighter-rouge">htmlentities</code> and <code class="language-plaintext highlighter-rouge">html_entity_decode</code> are <em>inverses</em> of each other, with completely different naming conventions.</li>
  </ul>
</blockquote>

<p>Seriously read through that entire article, it’s a great read.</p>

<h4 id="the-ecosystem">The Ecosystem</h4>

<p>The author mentions git but git is everywhere. Even Microsoft
<a href="https://github.com/WindowsAzure">uses</a>. <a href="https://github.com/NuGet">it</a>.
<a href="https://github.com/MSOpenTech">now</a>.</p>

<p>It’s great to see PHP has a dependency manager but so does everyone
else. The fact that you have one and it works decently does not mean
it’s better than another language or implementation. At this point, not
having a dependency manager is a problem. All modern frameworks have
them including
<a href="https://rubygems.org/">RubyGems</a>,
<a href="http://pypi.python.org/pypi">Python PyPI</a>,
<a href="https://npmjs.org/">Node Package Manager (NPM)</a>,
<a href="http://nuget.org/">Microsoft NuGet</a>, etc.</p>

<p>I’m not sure how the author judges that Composer is better than other
package managers. Is it because it resolves dependencies or has over
2400 packages? I don’t know. The other managers do handle dependencies
as well and the package counts as of today are:</p>

<ul>
  <li>Composer at 2,454</li>
  <li>RubyGems at 42,278</li>
  <li>PyPI at 22,860</li>
  <li>NuGet at 7,792</li>
</ul>

<p>Under the original article, the author calls this community
collaboration but really he’s making a point about the number of
packages available in PHP. Given the numbers, I expected a lot more from
PHP given the number of sites online and the years its been in service.</p>

<h4 id="the-community">The Community</h4>

<p>To me, the community is not about sharing code but more about the people
themselves. The developers make the community because they strive to
make everything better.  They don’t sit in their own echo chambers and
pat each other on the back, talking about how great this is about their
preferred language or that is about their favorite framework. They see
the shortcomings and they strive to address them while acknowledging
their strengths and weaknesses.</p>

<p>The main issue I see with PHP but also with some other communities is
the lack of sharing. Not just code but technical advise, support and
overall camaraderie. Some communities are getting better at it. Having
spent years in the Microsoft and Java ecosystems, I’ve seen it shape up
better in Microsoft but that’s because they have evangelists running
around doing their job. Java’s “ok” but Oracle hasn’t helped at all,
especially given recent lawsuits and Hudson vs Jenkins.</p>

<p>The JavaScript community is an interesting one and the developers within
it range from copy/paste scripters to those that thrive on functional
programming who have dived deep into <a href="http://coffeescript.org/">CoffeeScript</a>
or <a href="http://www.dartlang.org/">Dart</a> to know which OOP parts suck
compared to, say, <a href="http://gkz.github.com/LiveScript/">LiveScript</a>.</p>

<p>That wide variety of developers makes it harder to polarize the
community as a whole and makes it difficult for one great programmer to
recognize another. It’s similar to PHP.</p>

<h4 id="conclusion">Conclusion</h4>

<p>The language is not easy to learn if it’s inconsistent. The community
around your language and framework really matters; it allows you to
learn and grow to become a passionate programmer.</p>

<p>Ultimately, I suggest that all developers should learn more than one
language and framework. Get out your bubble and expand your horizon.</p>

<p>You can then choose the right language and the right platform for the
right problem. Just as long as it’s not PHP.</p>]]></content><author><name></name></author></entry><entry><title type="html">Being a Productive Developer at a New Startup</title><link href="https://davidsommers.com/2012/07/31/being-a-productive-developer-at-a-new-startup.html" rel="alternate" type="text/html" title="Being a Productive Developer at a New Startup" /><published>2012-07-31T00:00:00-04:00</published><updated>2012-07-31T00:00:00-04:00</updated><id>https://davidsommers.com/2012/07/31/being-a-productive-developer-at-a-new-startup</id><content type="html" xml:base="https://davidsommers.com/2012/07/31/being-a-productive-developer-at-a-new-startup.html"><![CDATA[<p>Meetups are always fun because you get to meet a wide range of
developers. A great event in NYC is <a href="http://www.meetup.com/nyc-on-rails/">Hacker Hours by NYC on Rails</a>
which offers programming help for any developer regardless of the
problem.</p>

<p>A developer I helped out there recently learned Rails and joined a new startup.
He asked me an interesting question regarding working at the new
startup:</p>

<blockquote>
  <p>The other dev is very productive. I feel very slow and unproductive.
Is this what it’s like every time you join a project? Everything is
just a friggin’ mess?</p>
</blockquote>

<h4 id="ignorance-or-incapable">Ignorance or Incapable?</h4>

<p>I think there’s a distinction between ignorance, which is not knowing
something because you haven’t had the opportunity, and being incapable,
meaning not being able to do it even though you know how. When I start
new projects, it takes hours and days of reading to get past the
ignorance stage. It takes even longer if they use external libraries or
APIs that I’ve not used before.</p>

<p>Don’t expect to be productive within days or even weeks. In larger
projects, it could take months.</p>

<h4 id="legacy-code">Legacy Code</h4>

<p>On some projects, the developers are disorganized and unskilled or even
advanced developers who used so much meta programming that the code
reverted itself into being even harder to read. I have to read through
the mess and the confusion to determine the program’s purpose and the
programmer’s intent.</p>

<p>A developer’s goal is to make the program meet the goals of the business
and it’s just as important to make it readable to other developers.</p>

<p>I think we would all agree that Ruby and Python reads easier than
Assembler though all languages are capable of meeting their business
needs. So you have to look at your highly productive developer and ask,
is she writing functioning code, readable code or both?</p>

<h4 id="falling-behind">Falling behind</h4>

<p>From the new developer:</p>

<blockquote>
  <p>I’m burning out because I stare at “magic” all day and produce at a
snail’s pace. I don’t get the reward of finishing something frequently
and it decreases my morale.</p>
</blockquote>

<p>If pairing through large chunks of the application isn’t an option, I
suggest focusing on something small that’s part of the whole and working
on it until you grok it. You can also start on a new section of code
based on technology you already know.</p>

<p>Starting on a new project with lots of existing code and developers
can be a daunting proposition. Your first commit will probably be a bug
fix or small tweak.</p>

<p>Personally, I always start in the tests. That’s an easy place to
find the interesting business logic and the uninteresting CRUD code.
Test code needs to be refactored and structured just as much as the code
it tests which provides another opportunity to learn more about the
codebase and remove more of that “magic”.</p>

<h4 id="doing-fast-vs-doing-it-right">Doing FAST vs Doing it RIGHT</h4>

<p>Make your code a shining example of how it should work. Don’t spend too
much time polishing it. Any true craftsman will want to continue working
on their craft and as they get better and learn more, you’ll look back
at your old code and wonder what you were thinking.</p>

<p>Any code written more than an hour ago is crap and will eventually be
revisited. One of the Ruby Rogues said that all code is experimental.</p>

<p>Therefore, take the time to do it right. Don’t try to play catch-up and
throw away quality in favor of speed.</p>

<p>The difference between that highly productive developer that writes
messy code and the code that you write starts to shift the culture from
one of productive mess which inherently contains technical debt to quality
code that’s easier to work with later.</p>

<p>It’s always better to know about the engineering culture before you sign
the dotted line but if it’s truly a startup, then you can shape its
future.</p>

<h4 id="readable-code">Readable code</h4>

<p>Recently there was a popular gist from a contractor that was let go due to
applying aggressive SRP to a simple PostComment class in Ruby. The
thread is a good read but when I think of the new developer’s comments
regarding “magic” code, I think of something a previous co-worker,
<a href="https://gist.github.com/2838490#gistcomment-362468">Donald Ball</a>, wrote:</p>

<blockquote>
  <p>… getting code to do what it’s supposed to do is the lowest
threshold for acceptability in my book. Getting code to clearly
communicate its intent and to be easy and safe to modify is a higher
bar, but is the standard by which I judge any code that’s intended to
work for longer than a day. We spend more time reading code than
writing code.</p>
</blockquote>]]></content><author><name></name></author></entry></feed>