<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Geek Life</title>
        <link>https://geeklife.in.ua</link>
        <description>A literary, self-ironic dev blog</description>
        <lastBuildDate>Mon, 08 Jun 2026 07:27:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Oleksandr Kucherenko</copyright>
        <item>
            <title><![CDATA[Monaco 2026: One Overtake, All the Drama in the World]]></title>
            <link>https://geeklife.in.ua/monaco-2026-drama/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/monaco-2026-drama/</guid>
            <pubDate>Sun, 07 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[One overtake in two hours, six pit-lane penalties, two heroes in the same wall, and a broken car on the podium. Everyone says Monaco is boring. My wife and I had the time of our lives.]]></description>
            <content:encoded><![CDATA[<p>One overtake. In two hours of Monaco, a car passed another car on track exactly once — and that was Arvid Lindblad, surfing a wagon-load of pure luck into the points. One move, a fistful of points by the end, and if the red flag hadn't fallen exactly his way it would have been a far less cheerful afternoon for him. By every metric the purists love, this was a dead race.</p>
<p>It was also the most fun my wife and I have had in front of a screen all season.</p>
<p>Kimi Antonelli won it, by the way — lights to flag, utterly in control. I'd genuinely forgotten he won, and honestly nobody gave a damn — and that tells you everything about where the story actually lived.</p>
<h2>A race with one overtake and a hundred plot twists</h2>
<p>Monaco isn't a race you win. It's a race you survive. And this year the survival rate was comedy.</p>
<h2>Hadjar drove a wreck onto the podium</h2>
<p>Isack Hadjar spent what felt like sixty laps nursing a broken Red Bull around the houses, collecting what felt like every penalty in the world on the way. Then the stewards quietly dropped the one that mattered — the team had started illegal work on his car under the red flag and then thought better of it — and he climbed out of that mess onto his first podium for Red Bull. P3. Not because he was fast, but because everyone ahead of him found a more creative way to lose.</p>
<p>George Russell got the opposite gift. A five-second pit-lane penalty he couldn't even serve properly — because under the late safety car one half of the pit wall thought they had to sit out the five seconds and the other half thought they didn't, so the crew just went ahead and changed the tyres while George sat there asking the radio whether he was even stopping. The FIA was not amused, and the five seconds became a drive-through that folded his afternoon shut like a cheap deck chair. Two drivers, the same circuit, opposite endings. That's the whole sport in one stop.</p>
<h2>Stroll started it, Leclerc had to one-up him</h2>
<p>Lance Stroll planted his Aston in the barriers first — first to get bored, evidently, first to decide he'd seen quite enough and head home early. Then Charles Leclerc, apparently deciding the drama wasn't quite sharp enough, repeated the trick almost note for note at the restart — and binned a podium doing it. The FIA pointed at the track surface breaking up at exactly the spot where both of them lost the car. Both drivers shrugged and blamed their own machinery — engine braking, brakes, take your pick. Nobody agreed on anything, which is the most Monaco outcome imaginable.</p>
<p>And the Ferrari heartbreak underneath it all: Leclerc, at home, throwing away a podium car. Rage? An honest mistake? He came out of it sad and furious in equal measure, and you genuinely couldn't tell which one put him in the wall. Piastri, for the record, dragged his McLaren home a frustrated fourth — a podium that was right there and simply never arrived.</p>
<h2>Six speeding tickets in one afternoon</h2>
<p>And the penalties. Oh, the penalties. Six drivers — <em>six</em> — got done for speeding in the pit lane. Half the grid, near enough, all caught doing the one thing every team has a literal button to prevent.</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span>THE PENALTY BOX · Monaco 2026</span></span>
<span class="line"><span>Gasly       P3 on the road  ->  P7   (two separate 5s pit-lane penalties)</span></span>
<span class="line"><span>Russell     5s pit-lane     ->  drive-through, afternoon over</span></span>
<span class="line"><span>Hulkenberg  10s             ->  for collecting Sainz</span></span>
<span class="line"><span>Perez       false start     ->  P10 stripped, sent to the back</span></span>
<span class="line"><span>... and two more for the road. Six speeding tickets in total.</span></span></code></pre>
<p>So which is it? Did half the paddock set their limiters wrong on the same day, or did something go sideways on the FIA's side of the measurement? They're usually surgical about this stuff. This time something smells off. Gasly is the one I ache for — he crossed the line third for Alpine and got demoted to seventh by two separate five-second penalties. A Monaco podium, gone, over a handful of km/h.</p>
<p>I haven't even named everyone who got burned. Carlos Sainz — forgot him completely, poor guy, just unlucky from lights to flag. Hülkenberg picked up a penalty for tipping Carlos into trouble. The casualty list reads like a phone book.</p>
<h2>The footnotes nobody clapped for</h2>
<p>Nearly forgot the best little story of all: the final point. Fernando Alonso, P10 — Aston Martin's first point and first top-10 of the entire 2026 season. Let that land. Their year is so grim that one point, handed to them by someone else's penalty, counts as a party. They will not get this lucky twice.</p>
<p>Then there's Pérez, in a league of his own — only the league turned out to be the losers' one. How do you line up in the wrong grid box, <em>and then</em> fumble the restart, knowing full well you've already been warned? He was genuinely quick — close to winning the loser's cup outright — and then a false start dropped him to the back and stripped his P10, denying Cadillac their first-ever point. Second place in the championship nobody wants.</p>
<p>But honestly? Cadillac keep getting better and the Astons keep getting worse, so my prediction is they level it out at the bottom. And as much as Alonso charms me, it's no comfort — I reckon he and Stroll finish dead last in this loser's championship, together.</p>
<div class="section-divider"><span class="section-divider-ornament">***</span></div>
<h2>What Monaco actually needs</h2>
<blockquote>
<p>Everyone's begging for more overtakes at Monaco. Wrong fix. We don't want more passing — we want more carnage. And this year Monaco delivered.</p>
</blockquote>
<p>Here's my unpopular take, and my wife co-signs it. People moan that Monaco needs eight overtakes, ten, fifteen, whatever number makes a spreadsheet smile. Others want the V8s screaming back, as if engine noise ever won anyone a race. I could not care less about any of it. Give us drama. Give us a broken car on the podium, two heroes in the same wall, six speeding tickets, and a first point that feels like a trophy.</p>
<p>That is what makes this thing watchable for hamsters like us.</p>
<p>More drama. More spectacle. That's the whole review.</p>]]></content:encoded>
            <author>Oleksandr Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Testing Literary Elements]]></title>
            <link>https://geeklife.in.ua/test-literary-elements/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/test-literary-elements/</guid>
            <pubDate>Tue, 20 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A test article exercising pull quotes, section dividers, and literary formatting alongside code blocks.]]></description>
            <content:encoded><![CDATA[<p>Good writing is not just about what you say -- it is about how the page breathes. The spaces between sections, the weight of a quoted phrase pulled into the margin, the rhythm of short paragraphs alternating with longer ones. This article tests whether our pipeline preserves that rhythm.</p>
<p>We begin with ordinary prose. Nothing fancy. Just words doing their job, marching left to right across the screen like obedient little soldiers. But prose alone does not make a literary blog. We need emphasis, contrast, and the occasional dramatic pause.</p>
<aside class="pull-quote"><p>The best code, like the best prose, is the kind you do not notice. It simply works, and you move on, unaware that someone spent three hours naming a variable.</p></aside>
<p>That pull quote above should render as a visually distinct element -- larger text, perhaps indented or styled differently from the body. It is the literary equivalent of a developer pausing mid-code-review to say something profound about naming conventions.</p>
<h2>The Art of the Divider</h2>
<p>Section dividers are not mere horizontal lines. In a literary blog, they represent a shift in thought, a change in tempo, a breath between movements. We use the thematic break syntax for this purpose.</p>
<div class="section-divider"><span class="section-divider-ornament">***</span></div>
<p>And here we are, on the other side of the divider. The topic has shifted. The mood is different. Perhaps the font has not changed, but the reader's mind has reset. That tiny visual gap does more work than a thousand transition words.</p>
<p>Let us also verify that code blocks coexist peacefully with literary elements:</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#F97316">function</span><span style="color:#FDE68A"> breathe</span><span style="color:#8A7E72">(</span><span style="color:#D4C8B8">section</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> string</span><span style="color:#8A7E72">)</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> string</span><span style="color:#8A7E72"> {</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic">  // Sometimes the best thing a function can do</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic">  // is add a little space.</span></span>
<span class="line"><span style="color:#F97316">  return</span><span style="color:#8A7E72"> `</span><span style="color:#FDA4AF">\n</span><span style="color:#84CC16">---</span><span style="color:#FDA4AF">\n\n</span><span style="color:#8A7E72">${</span><span style="color:#D4C8B8">section</span><span style="color:#8A7E72">}</span><span style="color:#FDA4AF">\n</span><span style="color:#8A7E72">`</span><span style="color:#8A7E72">;</span></span>
<span class="line"><span style="color:#8A7E72">}</span></span></code></pre>
<p>Code and prose should complement each other in a dev blog. The code shows how; the prose explains why. Together, they create something neither could achieve alone.</p>
<aside class="pull-quote"><p>Writing about code is like dancing about architecture -- theoretically absurd, practically essential, and occasionally beautiful when done well.</p></aside>
<h2>Final Thoughts</h2>
<p>This article has tested three critical literary features: pull quotes for emphasis, section dividers for pacing, and the coexistence of code blocks with literary prose. If all three render correctly, our pipeline handles the full spectrum of content this blog will publish.</p>
<p>The word count target for this article is approximately four hundred words, giving us a meaningful reading time estimate. Literary elements should not inflate the word count -- the pipeline should count only the actual prose, not the directive syntax or code block markers.</p>
<p>A blog that cannot handle pull quotes is just a README with ambition. And we have bigger ambitions than that.</p>]]></content:encoded>
            <author>Oleksandr Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Hello, Pipeline!]]></title>
            <link>https://geeklife.in.ua/hello-pipeline/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/hello-pipeline/</guid>
            <pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A test article for the content pipeline, exercising code blocks in multiple languages and basic markdown features.]]></description>
            <content:encoded><![CDATA[<p>Every blog needs a beginning, and every content pipeline needs its first test subject. This article exists to verify that our markdown processing works end-to-end: from raw frontmatter parsing through syntax highlighting to final HTML output.</p>
<p>Building a content pipeline is a lot like building plumbing. Nobody admires the pipes, but everyone notices when the water stops flowing. The same applies here -- if the pipeline works, readers see beautifully rendered articles. If it breaks, they see raw markdown and existential dread.</p>
<h2>Code Blocks</h2>
<p>Let us start with a TypeScript function that processes an article:</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#F97316">interface</span><span style="color:#FDBA74"> Article</span><span style="color:#8A7E72"> {</span></span>
<span class="line"><span style="color:#D4C8B8">  title</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> string</span><span style="color:#8A7E72">;</span></span>
<span class="line"><span style="color:#D4C8B8">  date</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> string</span><span style="color:#8A7E72">;</span></span>
<span class="line"><span style="color:#D4C8B8">  tags</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> string</span><span style="color:#8A7E72">[];</span></span>
<span class="line"><span style="color:#8A7E72">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97316">function</span><span style="color:#FDE68A"> processArticle</span><span style="color:#8A7E72">(</span><span style="color:#D4C8B8">raw</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> string</span><span style="color:#8A7E72">)</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> Article</span><span style="color:#8A7E72"> {</span></span>
<span class="line"><span style="color:#F97316">  const</span><span style="color:#D4C8B8"> frontmatter </span><span style="color:#F97316">=</span><span style="color:#FDE68A"> parseFrontmatter</span><span style="color:#8A7E72">(</span><span style="color:#D4C8B8">raw</span><span style="color:#8A7E72">);</span></span>
<span class="line"><span style="color:#F97316">  const</span><span style="color:#D4C8B8"> html </span><span style="color:#F97316">=</span><span style="color:#FDE68A"> renderMarkdown</span><span style="color:#8A7E72">(</span><span style="color:#D4C8B8">raw</span><span style="color:#8A7E72">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97316">  return</span><span style="color:#8A7E72"> {</span></span>
<span class="line"><span style="color:#D4C8B8">    title</span><span style="color:#8A7E72">:</span><span style="color:#D4C8B8"> frontmatter</span><span style="color:#8A7E72">.</span><span style="color:#D4C8B8">title</span><span style="color:#8A7E72">,</span></span>
<span class="line"><span style="color:#D4C8B8">    date</span><span style="color:#8A7E72">:</span><span style="color:#D4C8B8"> frontmatter</span><span style="color:#8A7E72">.</span><span style="color:#D4C8B8">date</span><span style="color:#8A7E72">,</span></span>
<span class="line"><span style="color:#D4C8B8">    tags</span><span style="color:#8A7E72">:</span><span style="color:#D4C8B8"> frontmatter</span><span style="color:#8A7E72">.</span><span style="color:#D4C8B8">tags </span><span style="color:#F97316">??</span><span style="color:#8A7E72"> [],</span></span>
<span class="line"><span style="color:#8A7E72">  };</span></span>
<span class="line"><span style="color:#8A7E72">}</span></span></code></pre>
<p>And here is how you might run the pipeline from the command line:</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#F59E0B;font-style:italic"># Build all articles</span></span>
<span class="line"><span style="color:#FDE68A">bun</span><span style="color:#84CC16"> run</span><span style="color:#84CC16"> build</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># Watch for changes during development</span></span>
<span class="line"><span style="color:#FDE68A">bun</span><span style="color:#84CC16"> run</span><span style="color:#84CC16"> dev</span><span style="color:#FDA4AF"> --watch</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># Check for broken links</span></span>
<span class="line"><span style="color:#FDE68A">bun</span><span style="color:#84CC16"> run</span><span style="color:#84CC16"> check:links</span></span></code></pre>
<p>Finally, a configuration example in JSON:</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#8A7E72">{</span></span>
<span class="line"><span style="color:#8A7E72">  "</span><span style="color:#D4C8B8">pipeline</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">:</span><span style="color:#8A7E72"> {</span></span>
<span class="line"><span style="color:#8A7E72">    "</span><span style="color:#D4C8B8">contentDir</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">:</span><span style="color:#8A7E72"> "</span><span style="color:#84CC16">content/articles</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">,</span></span>
<span class="line"><span style="color:#8A7E72">    "</span><span style="color:#D4C8B8">outputDir</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">:</span><span style="color:#8A7E72"> "</span><span style="color:#84CC16">dist</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">,</span></span>
<span class="line"><span style="color:#8A7E72">    "</span><span style="color:#D4C8B8">languages</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">:</span><span style="color:#8A7E72"> [</span><span style="color:#8A7E72">"</span><span style="color:#84CC16">en</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">,</span><span style="color:#8A7E72"> "</span><span style="color:#84CC16">ru</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">],</span></span>
<span class="line"><span style="color:#8A7E72">    "</span><span style="color:#D4C8B8">features</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">:</span><span style="color:#8A7E72"> {</span></span>
<span class="line"><span style="color:#8A7E72">      "</span><span style="color:#D4C8B8">syntaxHighlighting</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">:</span><span style="color:#FDA4AF"> true</span><span style="color:#8A7E72">,</span></span>
<span class="line"><span style="color:#8A7E72">      "</span><span style="color:#D4C8B8">pullQuotes</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">:</span><span style="color:#FDA4AF"> true</span><span style="color:#8A7E72">,</span></span>
<span class="line"><span style="color:#8A7E72">      "</span><span style="color:#D4C8B8">readingTime</span><span style="color:#8A7E72">"</span><span style="color:#8A7E72">:</span><span style="color:#FDA4AF"> true</span></span>
<span class="line"><span style="color:#8A7E72">    }</span></span>
<span class="line"><span style="color:#8A7E72">  }</span></span>
<span class="line"><span style="color:#8A7E72">}</span></span></code></pre>
<h2>Why This Matters</h2>
<p>The pipeline is the invisible backbone of the blog. Every article passes through it, so we test it thoroughly with articles like this one. If you are reading this rendered correctly, the pipeline works. If you are reading raw markdown, well, we have some debugging to do.</p>
<p>This article targets approximately three hundred words to give the reading time calculator something meaningful to work with. A one-paragraph article would calculate to zero minutes, which is technically accurate but philosophically unsatisfying.</p>]]></content:encoded>
            <author>Oleksandr Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Production Hotfix at Midnight]]></title>
            <link>https://geeklife.in.ua/production-hotfix-at-midnight/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/production-hotfix-at-midnight/</guid>
            <pubDate>Sat, 10 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[The anatomy of a midnight production incident: from the first alert to the postmortem nobody reads, and everything that goes wrong in between.]]></description>
            <content:encoded><![CDATA[<p>The phone buzzes at 11</p><div></div> PM. You know what it is before you look. PagerDuty has a distinctive vibration pattern -- three short pulses that your nervous system has learned to associate with adrenaline, dread, and the immediate need to find your laptop. You're already out of bed by the second pulse. Your partner mumbles something about "that thing again" and goes back to sleep. You envy their ability to not care about HTTP 500 errors.<p></p>
<p>The dashboard tells the story: response times spiked from 200ms to 12 seconds at 11</p><div></div> PM. Error rate jumped from 0.1% to 34%. The database connection pool is exhausted. Three microservices are in a cascading failure loop, each one timing out while waiting for the other. It's a distributed systems problem, which means it's everyone's problem and nobody's problem simultaneously.<p></p>
<h2>The War Room</h2>
<p>You join the incident Slack channel. Two other engineers are already there, one of whom has been debugging for five minutes and has already identified three incorrect hypotheses. This is normal. Incident response is 80% wrong guesses and 20% the right guess that seems obvious in retrospect. The trick is to guess fast and fail cheap.</p>
<p>Someone suggests rolling back the latest deploy. It went out at 4 PM -- seven hours ago. If the deploy caused this, why did it take seven hours to manifest? You check the deploy diff. Fourteen files changed, mostly frontend. One backend change: a database query optimization that removes an unused index scan. Could that be it? At midnight, everything is suspicious and nothing makes sense. You roll back anyway because rollbacks are cheap and certainty is expensive.</p>
<p>The rollback doesn't fix it. The error rate drops from 34% to 28%, which means the deploy made it worse but wasn't the root cause. Now you have two problems: the original issue and the emotional weight of having ruled out the easy fix.</p>
<h2>The Fix</h2>
<p>At 12</p><div></div> AM, someone notices that the database's disk usage hit 95% at 11<div></div> PM. A scheduled job that aggregates analytics data ran at 11<div></div> and produced a temporary table that consumed 40GB of disk space. The table was supposed to be cleaned up after processing, but the cleanup step failed silently because the disk was already at 90% and the temp table creation pushed it over the threshold. A classic feedback loop: the cleanup failed because of low disk space, and the disk space was low because the cleanup failed.<p></p>
<p>The fix is embarrassingly simple: delete the temp table, add disk space monitoring, make the cleanup step fail loudly. Three commands, two config changes, one alert rule. Total fix time: four minutes. Total incident time: forty-nine minutes. Total time spent in next week's postmortem meeting discussing how to prevent this from happening again: ninety minutes. The postmortem produces an action item to add better monitoring. The action item is assigned, prioritized as "medium," and joins the backlog where it will marinate until the next midnight incident reminds everyone why it was important.</p>
<p>You close your laptop at 12</p><div></div> AM, set your alarm for 7, and go back to bed knowing that somewhere in the backlog, a medium-priority ticket is quietly counting down to the next midnight phone call.<p></p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[The Weekend Side Project Curse]]></title>
            <link>https://geeklife.in.ua/weekend-side-project-curse/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/weekend-side-project-curse/</guid>
            <pubDate>Sat, 22 Apr 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Why every developer has a graveyard of unfinished side projects, and why we keep starting new ones despite overwhelming evidence that we won't finish them either.]]></description>
            <content:encoded><![CDATA[<p>I have a folder on my hard drive called "Projects." It contains forty-seven subdirectories. Three of them are finished. The rest are monuments to enthusiasm -- ambitious starts, creative sparks, and weekend afternoons spent setting up build tools for applications that never got built. Each project has a moment of conception I remember vividly and a moment of abandonment I can't pinpoint. They didn't die. They just stopped being worked on, which is the project equivalent of a cold case file.</p>
<p>The pattern is always the same. Friday evening: a brilliant idea strikes. This will be the one. A game engine in Rust, a markdown editor with AI integration, a budgeting app that actually works. Saturday morning: you set up the repository, choose the tech stack, configure linting, write a comprehensive README for a project that doesn't exist yet. Saturday afternoon: you build the core feature and it works. The dopamine hits. You're a genius. Sunday: you start on the second feature and realize the architecture you chose on Saturday doesn't support it. You'd need to refactor. Refactoring isn't fun. You open Twitter. Monday: back to your day job. The side project joins the graveyard.</p>
<h2>The Real Problem</h2>
<p>The real problem isn't lack of time or discipline. It's that starting a project and finishing a project require completely different skills, and developers are disproportionately good at starting. Starting is creative, exploratory, and full of possibilities. Finishing is administrative, tedious, and full of edge cases. Starting is choosing a color palette. Finishing is making sure the color palette works for colorblind users in dark mode on a 4K display with browser zoom set to 125%.</p>
<p>My game projects are the worst offenders. I've started at least five games and finished exactly one -- and that one only because it was for a game jam with a hard deadline. Without external pressure, every game reaches the 70% point where it's playable but not polished, functional but not fun, and the remaining 30% is pure slog: menu screens, save systems, sound effects, and that one collision detection bug that only happens when you approach an obstacle from the northeast at exactly 7.3 pixels per frame.</p>
<h2>Making Peace</h2>
<p>I've stopped feeling guilty about unfinished side projects. Each one taught me something. The Rust game engine taught me about memory management. The markdown editor taught me about text rendering. The budgeting app taught me that I don't actually want to track my spending, I just wanted to build a budgeting app. The journey was the destination, which is a cliche, but cliches become cliches because they're true enough to survive repetition.</p>
<p>Besides, folder forty-eight is looking really promising. It's a static site generator for a personal blog. I've set up the repository, chosen the tech stack, and written a comprehensive README. This time will be different. I can feel it.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[The Keyboard Shortcuts Obsession]]></title>
            <link>https://geeklife.in.ua/keyboard-shortcuts-obsession/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/keyboard-shortcuts-obsession/</guid>
            <pubDate>Wed, 15 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[How I spent more time learning IDE shortcuts than the shortcuts will ever save me, and why I'd do it again in a heartbeat.]]></description>
            <content:encoded><![CDATA[<p>I have a confession. I've spent roughly forty hours of my life learning keyboard shortcuts. Not using them productively -- learning them. Practicing them. Printing cheat sheets and taping them to my monitor. Watching YouTube tutorials where someone navigates an entire codebase without touching a mouse, their fingers dancing across the keyboard like a concert pianist performing Rachmaninoff, except instead of music they're producing a perfectly formatted React component.</p>
<p>At some point, this crossed the line from productivity optimization to hobby. I know this because I started learning shortcuts for actions I perform maybe twice a month. "Move line down" -- sure, useful. "Transpose two characters" -- occasionally handy. "Open the diff view for the currently selected file in the third split pane while filtering by staged changes only" -- okay, now I'm just collecting achievements.</p>
<h2>The Efficiency Paradox</h2>
<p>Here's the math that keyboard shortcut enthusiasts don't want you to see. Let's say a shortcut saves you two seconds compared to using a menu. Let's say you use that shortcut ten times a day. That's twenty seconds per day, about seven minutes per month, roughly an hour and a half per year. Sounds good, right? Except learning the shortcut took fifteen minutes of practice, so the break-even point is after about six weeks. For common shortcuts, this math works beautifully. For obscure shortcuts, you'll recoup your investment sometime around the heat death of the universe.</p>
<p>But efficiency isn't really the point, is it? The point is flow. When you know your shortcuts, there's no friction between thought and action. You think "rename this variable" and your fingers press <code>F2</code> before the thought is fully formed. You think "go to definition" and you're already there. The tool disappears, and you're working directly with the code instead of working with the tool that works with the code. That layer of indirection matters more than the seconds saved.</p>
<h2>The Dark Side</h2>
<p>The dark side of keyboard shortcut obsession is what happens when you use someone else's computer. You sit down, press your muscle-memory shortcuts, and everything goes wrong. Their IDE is configured differently. Their keybindings are default. They use -- and I say this with the gravity it deserves -- a mouse. You watch them right-click, navigate a menu, and select "Rename Symbol" with the patience of a person who has never known a better way. It takes four seconds. Four seconds! That's four seconds of their life they'll never get back, and you want to tell them, but you also know that proselytizing keyboard shortcuts is the developer equivalent of being a vegan who announces it at every dinner party. So you say nothing, and it costs you more emotional energy than the shortcut would ever save.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Stack Overflow Driven Development]]></title>
            <link>https://geeklife.in.ua/stack-overflow-driven-dev/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/stack-overflow-driven-dev/</guid>
            <pubDate>Sat, 30 May 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[An honest examination of how much of modern software development is really just searching Stack Overflow, copying the accepted answer, and hoping for the best.]]></description>
            <content:encoded><![CDATA[<p>Let's be honest with each other. We're all adults here. A significant percentage of professional software development consists of the following workflow: encounter a problem, open a browser tab, type the error message into a search engine, click the first Stack Overflow link, scroll past the question, read the accepted answer, copy the code, paste it into your editor, and pray. This is not a shameful secret. This is industry standard practice.</p>
<p>I once tracked my browser history during a normal work day. I opened Stack Overflow forty-three times. Forty-three. That's roughly once every eleven minutes. Some visits were for genuine questions I couldn't answer from memory. Others were for things I absolutely knew but wanted to double-check. And a few were for things I've looked up so many times that I recognize the answer by the commenter's avatar before I read the text.</p>
<h2>The Copy-Paste Spectrum</h2>
<p>Not all Stack Overflow copying is equal. There's a spectrum:</p>
<p>At one end, you have the thoughtful developer who reads the answer, understands the underlying principle, adapts the solution to their specific context, and closes the tab enriched with new knowledge. At the other end, you have the developer who copies the entire answer including the variable names <code>foo</code> and <code>bar</code>, ships it to production, and closes the tab before the page finishes loading. Most of us oscillate between these extremes depending on the day, the deadline, and how many hours since our last coffee.</p>
<p>The dangerous middle ground is the developer who copies the code, makes it work, but doesn't understand why it works. This creates what I call "cargo cult code" -- code that functions correctly but for reasons nobody on the team can explain. It sits in the codebase like a mysterious artifact, and everyone is afraid to touch it because the last person who tried broke the deployment pipeline for two days.</p>
<h2>The Deeper Truth</h2>
<p>The real skill isn't knowing the answer. It's knowing how to find the answer and -- crucially -- how to evaluate whether the answer is correct, current, and applicable to your situation. A Stack Overflow answer from 2014 about JavaScript might reference practices that are now anti-patterns. An answer with 847 upvotes might be technically correct but architecturally inappropriate for your use case. An answer marked as accepted might have a comment from 2019 saying "this no longer works" with seventeen upvotes.</p>
<p>Reading Stack Overflow critically is itself a skill, and it's one that nobody teaches in computer science programs. Maybe they should. Call it "Applied Information Foraging" or "Practical Epistemology for Software Engineers." Until then, we'll keep copying, pasting, and hoping. At least we're honest about it.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Pair Programming as an Introvert]]></title>
            <link>https://geeklife.in.ua/pair-programming-introvert/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/pair-programming-introvert/</guid>
            <pubDate>Tue, 12 Mar 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[The introvert's field guide to pair programming: how to survive, contribute, and occasionally enjoy the experience of thinking out loud next to another human.]]></description>
            <content:encoded><![CDATA[<p>I'm an introvert who pair programs. If that sounds contradictory, that's because it is. Pair programming asks you to do three things that introverts find exhausting: think out loud, collaborate in real-time, and share a screen with someone who can see every typo, every wrong turn, every moment where you stare at a variable name for thirty seconds because your brain went on a coffee break.</p>
<p>The first time I pair programmed, I was the navigator. My job was to think strategically while the driver typed. Instead, I spent the entire session trying to formulate my thoughts quickly enough to say them before they became irrelevant. By the time I'd composed a perfectly structured suggestion, the driver had already solved the problem a different way. I contributed nothing except moral support and the occasional "yeah, that looks right."</p>
<h2>Finding a Rhythm</h2>
<p>It took about a dozen sessions before I found a rhythm that worked. The key insight was that pair programming isn't a conversation -- it's a jam session. You don't need to talk constantly. Silence is fine. Thinking is fine. The extrovert driver fills the silence naturally, narrating their thought process like a cooking show host explaining why they're adding paprika. The introvert navigator listens, processes, and interjects when something important comes up. Both roles are necessary. Both are productive.</p>
<p>The switching ritual helps too. When you swap driver and navigator roles every thirty minutes, you get predictable breaks from the spotlight. You know that in fifteen minutes, the pressure to perform will shift to the other person, and you can retreat into the comfortable role of observer. It's structured turn-taking, and introverts love structure almost as much as they love being left alone.</p>
<h2>The Unexpected Benefits</h2>
<p>Here's what nobody tells introverts about pair programming: it's actually less socially draining than meetings. In a meeting, you perform social rituals -- small talk, turn-taking, the delicate dance of interrupting without seeming rude. In pair programming, the code is the conversation. You talk about functions, not feelings. You debate architecture, not office politics. For someone who finds social interaction tiring but intellectual discussion energizing, pair programming hits a sweet spot.</p>
<p>I still can't do it all day. Four hours of pairing leaves me as drained as eight hours of meetings. But those four hours produce more and better code than I'd write alone, and that's the trade-off I've learned to accept. The best code I've ever written had two authors. It also had twice the commit messages arguing about semicolons, but that's a story for another post.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[The Dark Arts of Regular Expressions]]></title>
            <link>https://geeklife.in.ua/regex-dark-arts/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/regex-dark-arts/</guid>
            <pubDate>Thu, 05 Jul 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[Some people, when confronted with a problem, think 'I know, I'll use regular expressions.' Now they have two problems. Here's a field guide to both.]]></description>
            <content:encoded><![CDATA[<p>There's a famous quote attributed to Jamie Zawinski: "Some people, when confronted with a problem, think 'I know, I'll use regular expressions.' Now they have two problems." I used to think this was a warning. Now I realize it's a description of my typical Tuesday.</p>
<p>Regular expressions are the closest thing programming has to magic spells. They're powerful, cryptic, write-only, and if you get one character wrong, the results range from "nothing happens" to "the server catches fire." A regex that validates email addresses looks like a cat walked across the keyboard during an earthquake. And yet, it works. Somehow. Nobody knows why. Nobody dares change it.</p>
<h2>A Field Guide</h2>
<p>Let's start with something simple. You need to match a phone number. Easy, right?</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#F59E0B;font-style:italic">// Attempt 1: The optimist</span></span>
<span class="line"><span style="color:#8A7E72">/</span><span style="color:#F59E0B">\d{3}-\d{3}-\d{4}</span><span style="color:#8A7E72">/</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F59E0B;font-style:italic">// Attempt 2: The realist (people use spaces, dots, parentheses...)</span></span>
<span class="line"><span style="color:#8A7E72">/[</span><span style="color:#FDA4AF">\(</span><span style="color:#8A7E72">]</span><span style="color:#F59E0B">?\d{3}</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">\)\-\.</span><span style="color:#F59E0B">\s</span><span style="color:#8A7E72">]</span><span style="color:#F59E0B">?\s?\d{3}</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">\-\.</span><span style="color:#F59E0B">\s</span><span style="color:#8A7E72">]</span><span style="color:#F59E0B">?\d{4}</span><span style="color:#8A7E72">/</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F59E0B;font-style:italic">// Attempt 3: The person who has seen international phone numbers</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic">// [abandoned, seeking therapy]</span></span></code></pre>
<p>The progression from Attempt 1 to Attempt 3 mirrors the five stages of grief, compressed into a single afternoon. You start with confidence, encounter reality, and end up questioning whether phone numbers are even a coherent concept.</p>
<h2>The Readability Problem</h2>
<p>The real issue with regex isn't capability -- it's readability. A regex is a compressed algorithm, and compression always trades clarity for density. Consider this pattern that validates dates:</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#8A7E72">/</span><span style="color:#F97316">^</span><span style="color:#8A7E72">(?:(?:</span><span style="color:#F59E0B">31</span><span style="color:#8A7E72">(</span><span style="color:#FDA4AF">\/</span><span style="color:#F97316">|</span><span style="color:#F59E0B">-</span><span style="color:#F97316">|</span><span style="color:#FDA4AF">\.</span><span style="color:#8A7E72">)(?:</span><span style="color:#F59E0B">0?</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">13578</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#F59E0B">1</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">02</span><span style="color:#8A7E72">]))</span><span style="color:#F97316">\1|</span><span style="color:#8A7E72">(?:(?:</span><span style="color:#F59E0B">29</span><span style="color:#F97316">|</span><span style="color:#F59E0B">30</span><span style="color:#8A7E72">)(</span><span style="color:#FDA4AF">\/</span><span style="color:#F97316">|</span><span style="color:#F59E0B">-</span><span style="color:#F97316">|</span><span style="color:#FDA4AF">\.</span><span style="color:#8A7E72">)(?:</span><span style="color:#F59E0B">0?</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">13-9</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#F59E0B">1</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">0-2</span><span style="color:#8A7E72">])</span><span style="color:#F97316">\2</span><span style="color:#8A7E72">))(?:(?:</span><span style="color:#F59E0B">1</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">6-9</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">2-9</span><span style="color:#8A7E72">]</span><span style="color:#F59E0B">\d</span><span style="color:#8A7E72">)</span><span style="color:#F59E0B">?\d{2}</span><span style="color:#8A7E72">)</span><span style="color:#F97316">$|^</span><span style="color:#8A7E72">(?:</span><span style="color:#F59E0B">29</span><span style="color:#8A7E72">(</span><span style="color:#FDA4AF">\/</span><span style="color:#F97316">|</span><span style="color:#F59E0B">-</span><span style="color:#F97316">|</span><span style="color:#FDA4AF">\.</span><span style="color:#8A7E72">)</span><span style="color:#F59E0B">0?2</span><span style="color:#F97316">\3</span><span style="color:#8A7E72">(?:(?:(?:</span><span style="color:#F59E0B">1</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">6-9</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">2-9</span><span style="color:#8A7E72">]</span><span style="color:#F59E0B">\d</span><span style="color:#8A7E72">)</span><span style="color:#F59E0B">?</span><span style="color:#8A7E72">(?:</span><span style="color:#F59E0B">0</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">48</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">2468</span><span style="color:#8A7E72">][</span><span style="color:#FDA4AF">048</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">13579</span><span style="color:#8A7E72">][</span><span style="color:#FDA4AF">26</span><span style="color:#8A7E72">])</span><span style="color:#F97316">|</span><span style="color:#8A7E72">(?:(?:</span><span style="color:#F59E0B">16</span><span style="color:#F97316">|</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">2468</span><span style="color:#8A7E72">][</span><span style="color:#FDA4AF">048</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">3579</span><span style="color:#8A7E72">][</span><span style="color:#FDA4AF">26</span><span style="color:#8A7E72">])</span><span style="color:#F59E0B">00</span><span style="color:#8A7E72">))))</span><span style="color:#F97316">$|^</span><span style="color:#8A7E72">(?:</span><span style="color:#F59E0B">0?</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">1-9</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#F59E0B">1\d</span><span style="color:#F97316">|</span><span style="color:#F59E0B">2</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">0-8</span><span style="color:#8A7E72">])(</span><span style="color:#FDA4AF">\/</span><span style="color:#F97316">|</span><span style="color:#F59E0B">-</span><span style="color:#F97316">|</span><span style="color:#FDA4AF">\.</span><span style="color:#8A7E72">)(?:(?:</span><span style="color:#F59E0B">0?</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">1-9</span><span style="color:#8A7E72">])</span><span style="color:#F97316">|</span><span style="color:#8A7E72">(?:</span><span style="color:#F59E0B">1</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">0-2</span><span style="color:#8A7E72">]))</span><span style="color:#F97316">\4</span><span style="color:#8A7E72">(?:(?:</span><span style="color:#F59E0B">1</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">6-9</span><span style="color:#8A7E72">]</span><span style="color:#F97316">|</span><span style="color:#8A7E72">[</span><span style="color:#FDA4AF">2-9</span><span style="color:#8A7E72">]</span><span style="color:#F59E0B">\d</span><span style="color:#8A7E72">)</span><span style="color:#F59E0B">?\d{2}</span><span style="color:#8A7E72">)</span><span style="color:#F97316">$</span><span style="color:#8A7E72">/</span></span></code></pre>
<p>This validates dates including leap years. It is correct. It is also an abomination. If you need to modify it, you don't debug it -- you delete it and start over. Or, better yet, you use a date parsing library like a reasonable person and save the regex for problems that actually deserve it.</p>
<h2>The Wisdom</h2>
<p>After years of writing, debugging, and cursing at regular expressions, I've arrived at one piece of wisdom: use regex for pattern matching, not parsing. Matching "does this string look like an email?" is a regex problem. Parsing "extract the semantic components of this email address" is not. The boundary between matching and parsing is where regex transforms from a useful tool into a Lovecraftian horror. Stay on the right side of that boundary, and regex is your friend. Cross it, and may your stack traces be merciful.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Docker Container Therapy]]></title>
            <link>https://geeklife.in.ua/docker-container-therapy/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/docker-container-therapy/</guid>
            <pubDate>Sat, 20 Jan 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[How Docker promised to solve 'works on my machine' and instead gave us a whole new category of problems to debug, plus the joy of multi-stage builds.]]></description>
            <content:encoded><![CDATA[<p>"Works on my machine" used to be a developer's last line of defense. A magical incantation that deflected blame from your code to the mysterious forces of environment configuration. Docker was supposed to kill that phrase. Package your app in a container, ship the container, and it works everywhere. In theory, this is elegant. In practice, I've spent more time debugging Dockerfiles than I ever spent debugging environment issues.</p>
<p>My first Dockerfile was eleven lines long and took forty-five minutes to build. My current Dockerfile is sixty-three lines long and takes four minutes to build. The journey between those two points involved learning about layer caching, multi-stage builds, Alpine versus Debian base images, and the hard way that <code>apt-get update</code> and <code>apt-get install</code> must be on the same <code>RUN</code> line or your cache will betray you.</p>
<h2>The Layer Cake</h2>
<p>Docker images are built in layers, and understanding layers is the difference between a 50MB image and a 2GB image. Every <code>RUN</code>, <code>COPY</code>, and <code>ADD</code> instruction creates a new layer. If you <code>RUN apt-get install build-essential</code> in one layer and <code>RUN apt-get purge build-essential</code> in the next, congratulations -- your image contains both the installation and the removal, because layers are immutable. It's like trying to lose weight by eating a cake and then throwing away the receipt.</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#F59E0B;font-style:italic"># Bad: 1.2GB image</span></span>
<span class="line"><span style="color:#F97316">FROM</span><span style="color:#D4C8B8"> node:18</span></span>
<span class="line"><span style="color:#F97316">COPY</span><span style="color:#D4C8B8"> . .</span></span>
<span class="line"><span style="color:#F97316">RUN</span><span style="color:#D4C8B8"> npm install</span></span>
<span class="line"><span style="color:#F97316">RUN</span><span style="color:#D4C8B8"> npm run build</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># Better: 180MB image</span></span>
<span class="line"><span style="color:#F97316">FROM</span><span style="color:#D4C8B8"> node:18-alpine </span><span style="color:#F97316">AS</span><span style="color:#D4C8B8"> builder</span></span>
<span class="line"><span style="color:#F97316">WORKDIR</span><span style="color:#D4C8B8"> /app</span></span>
<span class="line"><span style="color:#F97316">COPY</span><span style="color:#D4C8B8"> package*.json ./</span></span>
<span class="line"><span style="color:#F97316">RUN</span><span style="color:#D4C8B8"> npm ci --production=false</span></span>
<span class="line"><span style="color:#F97316">COPY</span><span style="color:#D4C8B8"> . .</span></span>
<span class="line"><span style="color:#F97316">RUN</span><span style="color:#D4C8B8"> npm run build</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97316">FROM</span><span style="color:#D4C8B8"> node:18-alpine</span></span>
<span class="line"><span style="color:#F97316">WORKDIR</span><span style="color:#D4C8B8"> /app</span></span>
<span class="line"><span style="color:#F97316">COPY</span><span style="color:#D4C8B8"> --from=builder /app/dist ./dist</span></span>
<span class="line"><span style="color:#F97316">COPY</span><span style="color:#D4C8B8"> --from=builder /app/node_modules ./node_modules</span></span>
<span class="line"><span style="color:#F97316">CMD</span><span style="color:#D4C8B8"> [</span><span style="color:#84CC16">"node"</span><span style="color:#D4C8B8">, </span><span style="color:#84CC16">"dist/server.js"</span><span style="color:#D4C8B8">]</span></span></code></pre>
<p>Multi-stage builds are Docker's redemption arc. You use a fat image to build, then copy only the artifacts to a slim image for production. It's wasteful in the same way that scaffolding is wasteful -- you build it, use it, tear it down, and nobody sees it in the final product.</p>
<h2>The Philosophy</h2>
<p>Docker didn't eliminate environment problems. It containerized them. Instead of "works on my machine," we now say "works in my container," which is the same sentence wearing a different hat. But the hat matters. A container is reproducible, versioned, and shareable. Your machine configuration is none of those things. Trading one set of problems for a better set of problems is, I've learned, what most of engineering actually is.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Code Reviews: A Survival Guide]]></title>
            <link>https://geeklife.in.ua/code-reviews-survival-guide/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/code-reviews-survival-guide/</guid>
            <pubDate>Mon, 14 Aug 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[Navigating the delicate social dynamics of code reviews, where technical feedback meets human ego and nobody wants to be the person who blocks the merge.]]></description>
            <content:encoded><![CDATA[<p>Code reviews are the part of software development where we pretend that our feedback is purely technical and has nothing to do with personal style preferences, aesthetic opinions, or the fact that we would have written it completely differently. We dress up "I don't like this" in the language of best practices and pretend we're being objective. We're not, but the fiction is useful.</p>
<p>I've been on both sides of the review table. I've been the nervous junior developer whose first pull request received forty-seven comments, twelve of which were about variable naming. And I've been the senior reviewer who left forty-seven comments, twelve of which were about variable naming, and felt righteous about every single one. Both experiences were educational. Neither was comfortable.</p>
<h2>The Unwritten Rules</h2>
<p>Every team has unwritten code review rules. Here are some I've collected over the years:</p>
<p>"Nit:" means "I know this doesn't matter but I'm going to mention it anyway." It's the reviewer's way of saying "I spotted something that bothers me aesthetically but isn't worth blocking the merge over." The correct response is to fix it silently. The incorrect response is to argue about it. Both happen with equal frequency.</p>
<p>"Could we maybe..." is not a question. It's a polite command. When a senior developer writes "Could we maybe extract this into a separate function?", what they mean is "Extract this into a separate function." The question mark is a social lubricant, not an invitation to say no.</p>
<p>"I'm not sure about this approach" means "This approach is wrong and I'd like you to arrive at that conclusion yourself so I don't have to say it directly." This is the code review equivalent of Socratic dialogue, and it's equally frustrating for everyone involved.</p>
<h2>Finding the Balance</h2>
<p>The best code reviews I've participated in shared one quality: they were conversations, not verdicts. The reviewer asked genuine questions. The author explained their reasoning. Sometimes the reviewer learned something. Sometimes the author changed their approach. Often, both happened.</p>
<p>The worst code reviews were monologues -- long lists of demands with no context, or defensive responses that addressed the letter of each comment while ignoring its spirit. Code reviews work when both parties remember that they're collaborating on a shared codebase, not competing in a correctness tournament. But it's hard to remember that when someone leaves a comment on your carefully crafted function that just says "why."</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[npm Dependency Hell]]></title>
            <link>https://geeklife.in.ua/npm-dependency-hell/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/npm-dependency-hell/</guid>
            <pubDate>Tue, 28 Feb 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[A deep dive into the terrifying moment when you run npm install and 1,347 packages appear in your node_modules, three of which are maintained by a single person in Nebraska.]]></description>
            <content:encoded><![CDATA[<p>I ran <code>npm install</code> on a fresh project today. A simple web application -- Express server, a couple of API endpoints, nothing fancy. The install pulled in 1,347 packages. One thousand three hundred and forty-seven. For a project with twelve lines in its <code>package.json</code>. I stared at the number for a while, the way you stare at a restaurant bill that can't possibly be right but mathematically is.</p>
<p>The <code>node_modules</code> directory weighs 247 megabytes. That's larger than the operating system I grew up with. Somewhere in those 247 megabytes is a package called <code>is-odd</code> that checks if a number is odd. It has one dependency: <code>is-number</code>. Which has its own dependency tree. To determine if 7 is odd, we've assembled a small civilization of code.</p>
<h2>The Audit</h2>
<p>I ran <code>npm audit</code> because I enjoy pain. It reported 14 vulnerabilities: 3 critical, 5 high, 6 moderate. One of the critical vulnerabilities is in a package four levels deep in the dependency tree -- a dependency of a dependency of a dependency of something I actually chose to install. Fixing it requires updating the top-level package, which has a major version bump, which changes its API, which means rewriting the three places I use it.</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#FDE68A">$</span><span style="color:#84CC16"> npm</span><span style="color:#84CC16"> audit</span></span>
<span class="line"><span style="color:#FDE68A">found</span><span style="color:#FDA4AF"> 14</span><span style="color:#84CC16"> vulnerabilities</span><span style="color:#D4C8B8"> (6 </span><span style="color:#84CC16">moderate,</span><span style="color:#FDA4AF"> 5</span><span style="color:#84CC16"> high,</span><span style="color:#FDA4AF"> 3</span><span style="color:#84CC16"> critical</span><span style="color:#D4C8B8">)</span></span>
<span class="line"><span style="color:#FDE68A">run</span><span style="color:#8A7E72"> `</span><span style="color:#FDE68A">npm</span><span style="color:#84CC16"> audit fix</span><span style="color:#8A7E72">`</span><span style="color:#FDE68A"> to</span><span style="color:#84CC16"> fix</span><span style="color:#84CC16"> them,</span><span style="color:#84CC16"> or</span><span style="color:#8A7E72"> `</span><span style="color:#FDE68A">npm</span><span style="color:#84CC16"> audit fix </span><span style="color:#FDA4AF">--force</span><span style="color:#8A7E72">`</span><span style="color:#FDE68A"> to</span><span style="color:#84CC16"> accept</span><span style="color:#84CC16"> breaking</span><span style="color:#84CC16"> changes</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FDE68A">$</span><span style="color:#84CC16"> npm</span><span style="color:#84CC16"> audit</span><span style="color:#84CC16"> fix</span><span style="color:#FDA4AF"> --force</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># 45 packages changed</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># 3 new vulnerabilities introduced</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># npm audit fix has mass-created a time loop</span></span></code></pre>
<p>The <code>--force</code> flag is npm's version of "hold my beer." It fixes vulnerabilities by introducing different vulnerabilities, creating a conservation-of-bugs principle that would make a physicist proud.</p>
<h2>The Existential Question</h2>
<p>The JavaScript ecosystem has a dependency problem, and we all know it, and we all keep <code>npm install</code>-ing anyway. It's collective denial on an industrial scale. We trust that the person maintaining <code>left-pad</code> won't delete it (they did), that critical infrastructure packages are well-funded (they aren't), and that our lock files will protect us from supply chain attacks (they might, if we actually commit them).</p>
<p>I don't have a solution. Nobody does. But I've started a new habit: before adding a dependency, I look at the package's GitHub page. If it has one maintainer, no recent commits, and the README starts with "this package is no longer maintained," I write those twelve lines of code myself. It's not much, but my <code>node_modules</code> directory is now only 230 megabytes, and I sleep marginally better at night.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[New in the Collection: Descent Journeys in the Dark]]></title>
            <link>https://geeklife.in.ua/descent-journeys-in-the-dark/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/descent-journeys-in-the-dark/</guid>
            <pubDate>Sat, 27 Feb 2016 00:00:00 GMT</pubDate>
            <description><![CDATA[It finally arrived -- the long-awaited and controversial purchase. I really wanted it, but the price and reviews about its balance kept me from pulling the trigger. A bit of spontaneity and here it is.]]></description>
            <content:encoded><![CDATA[<p>It finally arrived -- the long-awaited and controversial purchase. I really wanted it, but the price and reviews about its balance kept me from pulling the trigger. A bit of spontaneity and here it is.</p>
<blockquote>
<p>In the darkest depths of Terrinoth, an ambitious overlord gathers his minions to lay siege on the world above. Only a small band of heroes, gifted with courage and power, will be able to save the land from the cold grip of domination. Now is the time to venture into the dark and unravel the overlord's plot before it's too late...</p>
</blockquote>
<p><img src="/descent-journeys-in-the-dark/images/dj-1.webp" alt="Descent board game box contents: rulebooks, campaign map, cards, dice, tokens, and miniatures laid out" loading="lazy"></p>
<h2>What the game is about</h2>
<p>The game is about the epic adventure of heroes in the dungeons of a Dark Evil Overlord who won't miss a chance to show the heroes who's boss. It's a game for 2--5 players -- where am I supposed to find that many friends? One player takes on the role of universal evil and becomes the Overlord. The rest become regular heroes, divided into classes and archetypes. Heroes roam dungeons, search for treasure, fight the Overlord's minions, buy equipment... in short, they go all out.</p>
<p>All combat is dice-rolling -- there are both melee and ranged attacks. And of course spells, lots of spells. Can't do without them -- it's fantasy, after all.</p>
<p>The game features both standalone missions and campaigns. A campaign is a set of individual missions tied together by an overarching story. Heroes can carry their hard-earned (read: looted) gear into the next mission, accumulating a small fortune by the end of the campaign. You won't finish a campaign in a single day -- it takes dozens of hours, and the game thoughtfully provides a save system: a notebook and pencil.</p>
<p><img src="/descent-journeys-in-the-dark/images/dj-5.jpg" alt="Campaign map board showing the land of Terrinoth with cities and quest locations" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-6.webp" alt="Descent rulebooks, campaign board, cards, dice, and token bags spread out on table" loading="lazy"></p>
<h2>Impressions</h2>
<p>Haven't actually played yet -- haven't even finished reading the rules (a 20-page booklet). But I'll definitely report back once I get the chance to try it.</p>
<p>The miniatures are impressive -- they're quite detailed and genuinely menacing. I liked the tile system: you lay out the dungeon map from tiles, and since there are so many of them, it gives the game great replayability and variety.</p>
<p><img src="/descent-journeys-in-the-dark/images/dj-2.webp" alt="Hero and monster miniatures: small gray heroes, cream creatures, and large red demons including a dragon" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-3.webp" alt="Close-up of a red Overlord monster miniature with detailed scales and claws" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-4.webp" alt="White and red dragon miniatures with spread wings towering over smaller figures" loading="lazy"></p>
<p>On the downside, there's the balance issue. From what I've read in the rules, playing the Overlord seems kind of dull, and the heroes almost always have the upper hand -- they're essentially immortal.</p>
<p><img src="/descent-journeys-in-the-dark/images/dj-7.webp" alt="All miniatures lined up with cards, dice, and token bags in front" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-8.webp" alt="Red winged demon miniature close-up standing on the rulebook cover" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-9.webp" alt="Cream-colored tentacled monster miniature close-up on the rulebook" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-10.webp" alt="Dungeon map tiles assembled: fiery caverns, stone halls, and treasure rooms" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-11.webp" alt="Dungeon tiles showing icy caves and underground corridors" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-12.webp" alt="Close-up of a dungeon tile with scattered treasure and skeleton details" loading="lazy"></p>
<p><img src="/descent-journeys-in-the-dark/images/dj-13.webp" alt="Full dungeon layout assembled from multiple interlocking map tiles" loading="lazy"></p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Starting Development of STDS]]></title>
            <link>https://geeklife.in.ua/stds/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/stds/</guid>
            <pubDate>Sat, 20 Feb 2016 00:00:00 GMT</pubDate>
            <description><![CDATA[Spring is coming and I'm getting restless. An idea was born: build a multiplayer top-down shooter in the browser with JavaScript, set in space. Working title: STDS (Space Top Down Shooter).]]></description>
            <content:encoded><![CDATA[<p>Spring is coming and I'm getting restless. An idea was born: build a multiplayer top-down shooter in the browser with JavaScript, set in space. Working title: STDS (Space Top Down Shooter).</p>
<h2>Main stack</h2>
<p>Plain JavaScript ES5</p>
<p>JS modules in CommonJS format (like Node.js, but on the frontend)</p>
<p><a href="https://webpack.github.io/">Webpack 1.x</a> -- not sure 2.x is solid yet; even the first branch is still rough (IMHO). We'll see, maybe we'll migrate.</p>
<p>Rendering by <a href="http://www.pixijs.com/">PIXI JS</a></p>
<p>Using <a href="http://phaser.io/">PHASER IO</a> as the game engine</p>
<p>CSS generated from <a href="http://lesscss.org/">Less</a> -- I just like it</p>
<p>You can follow the project's progress on GitHub: <a href="https://github.com/AlexTiTanium/Space-Top-Down-Shooter">STDS</a>. Too early to think about the server side, but it'll most likely be Node.js.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Bad Monday]]></title>
            <link>https://geeklife.in.ua/bad-monday/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/bad-monday/</guid>
            <pubDate>Sat, 05 Dec 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[It's 4 AM, insomnia got me again. Usually I go work. For me this is a blessed time of peace and quiet -- what more do you need to write beautiful, maintainable code?]]></description>
            <content:encoded><![CDATA[<p>It's 4 AM, insomnia got me again. Usually I go work. For me this is a blessed time of peace and quiet -- what more do you need to write beautiful, maintainable code?</p>
<p>Beautiful code... let's talk about that, shall we?</p>
<p>Recently I was listening to Radio-T (a popular Russian tech podcast) -- don't remember which episode, one of the recent ones -- where they discussed an article about outsourcing.</p>
<p>Yet another client decided to save money and ordered iOS app development from the cheapest company they could find on the internet. The result is obvious: a complete disaster. And he goes point by point explaining why, in his opinion, everything went wrong.</p>
<p>Of course nobody's willing to admit the correlation between price and quality. It must be something else, right? You want to believe you can get the best for five bucks an hour. Though you should understand that a big price tag doesn't guarantee anything either.</p>
<p>There was a lot of back-and-forth on this topic. Umputun's brief take was that there's a difference in mentality: for Americans, the result is everything, the process is nothing. For Russians, the process is everything, and the result is a side effect. For Indians, one thing's rubbish, another thing's rubbish -- life's too short to sweat it.</p>
<p>So I'm sitting there thinking: damn, there's something to this. Should you even bother with the process?</p>
<p>After all, the client needs results, and what am I doing? I'm fussing over the process. Sure, I fuss over it to produce the best result I'm capable of, but you could get results faster.</p>
<p>The task: write a simple autocomplete for a service. Just shove everything into the damn controller that renders the list of results and call it a day.</p>
<p>But no, I have to extract it into a separate controller, refactor it -- because someone will come along to maintain this, and what will they think of me?</p>
<p>And what about the server side? A normal, efficient programmer would just regex through the database and be done with it. Result achieved.</p>
<p>But no, we're "special." Everything has to be fast, and the autocomplete has to actually help you type something. Instead of regex search, let's build a prefix tree? And let's add tag weights to surface the most popular ones first. And of course we need tests and documentation.</p>
<p>And just like that, I'm deep in the weeds of process.</p>
<p>This is not results. The client doesn't need any of this. Not only doesn't need it, but it's actively harmful -- financially and deadline-wise. And in a year they'll rewrite the whole thing anyway, if anyone still cares.</p>
<p>Maybe subconsciously I realize I'm paid by the hour, and if I do it in one hour instead of ten, I'll earn less? No, that's not it -- I did the same thing back when I worked for a fixed price.</p>
<p>What's bad is that for me this is a conscious choice. I don't want to change, to adapt to the market. (Probably why the last time I looked for a job it took me about three months.)</p>
<p>Kill me, but I want to write proper code. And proper code takes time, and time is money. The conclusion: by all rights, I should be fired. In this unequal battle between process and results, I'm the broken gear.</p>
<p>Beautiful code to you all, friends!</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[When the Build Breaks]]></title>
            <link>https://geeklife.in.ua/when-the-build-breaks/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/when-the-build-breaks/</guid>
            <pubDate>Mon, 01 Dec 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[A chronicle of the five stages of grief as experienced through a CI/CD pipeline that refuses to turn green, and the lessons learned along the way.]]></description>
            <content:encoded><![CDATA[<p>The Slack notification arrives at 2</p><div></div> PM on a Friday. "Build failed on main." Five words that can ruin a weekend. You check the CI dashboard and see a wall of red. The build has been failing for three commits, which means three developers pushed without checking, which means the problem has been composting in the pipeline for about two hours. This is fine.<p></p>
<p>The first stage is denial. "It's probably a flaky test." You re-run the pipeline. It fails again, this time with a different error, which is somehow worse than the same error. At least a consistent failure has dignity. An inconsistent failure is chaos wearing a lab coat.</p>
<h2>The Investigation</h2>
<p>You start reading the build log. It's 2,400 lines long, and the actual error is on line 2,387, buried under a mountain of successful steps that the CI system reports with the enthusiasm of a golden retriever. "Step 1: SUCCESS! Step 2: SUCCESS! Step 3: SUCCESS!" And then, quietly, at the bottom: <code>TypeError: Cannot read properties of undefined (reading 'map')</code>.</p>
<p>The error is in a test file. The test was added in the second commit, depends on a utility function from the first commit, and the third commit refactored that utility function without updating the test. A classic three-body problem. Each commit is correct in isolation. Together, they're a disaster.</p>
<h2>The Fix</h2>
<p>You could fix the test. That's the obvious solution. But fixing a test on a Friday afternoon is like fixing a leaky pipe -- you touch one thing and three other things start leaking. Instead, you do the responsible thing: you fix the test, run the full suite locally, watch it pass, push with a commit message that reads "fix: update test after refactor," and close your laptop before anything else can go wrong.</p>
<p>The build turns green at 3</p><div></div> PM. You've spent forty-eight minutes on what amounts to changing two lines of code. But those two lines kept five developers from deploying for an entire afternoon, and that math -- forty-eight minutes of your time versus five person-hours of blocked productivity -- is why CI/CD pipelines matter, why keeping them green matters, and why breaking the build on a Friday should be a fireable offense. I'm mostly joking about that last part. Mostly.<p></p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[The Joy of TypeScript]]></title>
            <link>https://geeklife.in.ua/the-joy-of-typescript/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/the-joy-of-typescript/</guid>
            <pubDate>Sat, 07 Jun 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[How I went from 'types are for people who can't hold code in their head' to 'I will never write untyped JavaScript again' in about six months.]]></description>
            <content:encoded><![CDATA[<p>I resisted TypeScript for longer than I'd like to admit. My arguments were the usual ones: it's just JavaScript with extra steps, the compiler is slow, type annotations clutter the code, and real programmers don't need types because they can hold the entire program state in their head. That last one aged particularly poorly.</p>
<p>The turning point came on a Thursday afternoon when I spent four hours debugging a function that expected an array of user objects but was receiving an array of user IDs. The function didn't crash -- it silently produced wrong results, because JavaScript is nothing if not accommodating. It'll happily iterate over a string as if it were an array of characters, multiply <code>undefined</code> by 7 to get <code>NaN</code>, and concatenate a number with an object to produce <code>"42[object Object]"</code>. It's the language equivalent of a waiter who brings you whatever's in the kitchen and insists it's what you ordered.</p>
<h2>The Conversion</h2>
<p>My first TypeScript file was a disaster. I typed everything as <code>any</code> because the compiler kept yelling at me, and <code>any</code> made it stop. This is like solving a fire alarm problem by removing the batteries. It works in the sense that the noise stops. It doesn't work in any other sense.</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#F59E0B;font-style:italic">// Week 1: The any phase</span></span>
<span class="line"><span style="color:#F97316">function</span><span style="color:#FDE68A"> processData</span><span style="color:#8A7E72">(</span><span style="color:#D4C8B8">data</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> any</span><span style="color:#8A7E72">)</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> any</span><span style="color:#8A7E72"> {</span></span>
<span class="line"><span style="color:#F97316">  return</span><span style="color:#D4C8B8"> data</span><span style="color:#8A7E72">.</span><span style="color:#FDE68A">map</span><span style="color:#8A7E72">((</span><span style="color:#D4C8B8">item</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> any</span><span style="color:#8A7E72">)</span><span style="color:#F97316"> =></span><span style="color:#D4C8B8"> item</span><span style="color:#8A7E72">.</span><span style="color:#D4C8B8">value</span><span style="color:#8A7E72">);</span></span>
<span class="line"><span style="color:#8A7E72">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F59E0B;font-style:italic">// Week 4: The enlightenment</span></span>
<span class="line"><span style="color:#F97316">function</span><span style="color:#FDE68A"> processData</span><span style="color:#8A7E72">(</span><span style="color:#D4C8B8">data</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> UserRecord</span><span style="color:#8A7E72">[])</span><span style="color:#F97316">:</span><span style="color:#FDBA74"> number</span><span style="color:#8A7E72">[]</span><span style="color:#8A7E72"> {</span></span>
<span class="line"><span style="color:#F97316">  return</span><span style="color:#D4C8B8"> data</span><span style="color:#8A7E72">.</span><span style="color:#FDE68A">map</span><span style="color:#8A7E72">((</span><span style="color:#D4C8B8">item</span><span style="color:#8A7E72">)</span><span style="color:#F97316"> =></span><span style="color:#D4C8B8"> item</span><span style="color:#8A7E72">.</span><span style="color:#D4C8B8">value</span><span style="color:#8A7E72">);</span></span>
<span class="line"><span style="color:#8A7E72">}</span></span></code></pre>
<p>The real joy came about a month in, when the compiler caught a bug I was about to introduce. I'd renamed a field in an interface, and TypeScript immediately highlighted every file that referenced the old name. In JavaScript, that rename would have been a ticking time bomb -- working fine in development, exploding in production three weeks later when someone hit the one code path that used the old field name.</p>
<h2>The Verdict</h2>
<p>TypeScript doesn't make you a better programmer. It makes you an honest one. It forces you to think about your data shapes before you write your logic, to consider edge cases before they consider you, and to document your intentions in a way that a machine can verify. You still write bugs -- you just write different bugs. Higher-class bugs. Bugs with type safety.</p>
<p>I'll never go back to untyped JavaScript. Not because TypeScript is perfect, but because the alternative is holding the entire program state in my head, and my head has a garbage collector with very aggressive thresholds.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Refactoring Legacy Spaghetti]]></title>
            <link>https://geeklife.in.ua/refactoring-legacy-spaghetti/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/refactoring-legacy-spaghetti/</guid>
            <pubDate>Mon, 18 Nov 2013 00:00:00 GMT</pubDate>
            <description><![CDATA[Every developer inherits a codebase they didn't write. The question isn't whether to refactor -- it's how to do it without losing your mind or breaking everything.]]></description>
            <content:encoded><![CDATA[<p>I inherited a codebase last month. "Inherited" is the polite word. "Was abandoned with" is more accurate. The original developer left the company, taking with him the only brain that understood why the main service class has 4,200 lines, no tests, and a method called <code>doEverything()</code> that lives up to its name in the worst possible way.</p>
<p>The codebase has layers like geological strata. You can trace the history of web development through its dependencies: jQuery at the bottom, Angular 1 in the middle, a thin layer of React on top, and scattered throughout, raw DOM manipulation that predates all of them. It's an archaeological dig site masquerading as a web application.</p>
<h2>The Refactoring Dilemma</h2>
<p>Here's the thing about legacy code that nobody tells you: it works. That 4,200-line method? It processes ten thousand requests a day without failing. The tangled mess of jQuery and Angular? Users don't see it. They see a functioning application that does what it's supposed to do. From a business perspective, this code is perfect.</p>
<p>So why refactor? Because the next feature request will take three weeks instead of three days. Because every bug fix introduces two new bugs. Because the build takes fourteen minutes and nobody knows why. Because the new hire stared at the codebase for a week and then quietly updated their LinkedIn.</p>
<h2>The Strategy</h2>
<p>You don't refactor legacy code all at once. That's a rewrite, and rewrites fail. You refactor it the way you eat an elephant -- one bite at a time, while everyone around you wonders why you're eating an elephant at all.</p>
<p>Start at the edges. Find a function with clear inputs and outputs. Write a test for it. Refactor it. Move on. Don't touch <code>doEverything()</code> yet. Don't even look at it. It feeds on attention. When you've refactored enough of the periphery, <code>doEverything()</code> will be smaller by subtraction -- you'll have extracted its responsibilities one by one, like removing Jenga blocks from a tower that, against all odds, refuses to fall.</p>
<p>Three months in, I've extracted forty-two functions, written a hundred and twelve tests, and reduced the main class to 3,100 lines. It's still spaghetti. But it's spaghetti with unit tests, and that's practically fine dining.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Git Bisect Saves the Day]]></title>
            <link>https://geeklife.in.ua/git-bisect-saves-the-day/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/git-bisect-saves-the-day/</guid>
            <pubDate>Wed, 10 Apr 2013 00:00:00 GMT</pubDate>
            <description><![CDATA[A love letter to git bisect -- the underappreciated tool that turns a haystack of commits into a binary search for the needle that broke everything.]]></description>
            <content:encoded><![CDATA[<p>Something broke in production. Not the dramatic, everything-is-on-fire kind of broke. The subtle kind. A calculation that was off by 0.3%. A tooltip that appeared two pixels to the left. The kind of bug that makes you question your own sanity before you question the code.</p>
<p>The commit log showed 847 commits since the last known good state. Eight hundred and forty-seven. That's not a number you can reason about manually. That's not a number you should even try. That's a number for <code>git bisect</code>.</p>
<h2>Binary Search for Humans</h2>
<p>If you haven't used <code>git bisect</code>, here's the elevator pitch: it performs a binary search through your commit history. You tell it "this commit is good" and "this commit is bad," and it checks out the midpoint. You test, report good or bad, and it halves the search space. In a repository with 847 commits, you'll find the culprit in about ten steps. Ten. Not 847. Ten.</p>
<pre class="shiki warm-syntax" style="background-color:#181412;color:#d4c8b8"><code><span class="line"><span style="color:#FDE68A">git</span><span style="color:#84CC16"> bisect</span><span style="color:#84CC16"> start</span></span>
<span class="line"><span style="color:#FDE68A">git</span><span style="color:#84CC16"> bisect</span><span style="color:#84CC16"> bad</span><span style="color:#84CC16"> HEAD</span></span>
<span class="line"><span style="color:#FDE68A">git</span><span style="color:#84CC16"> bisect</span><span style="color:#84CC16"> good</span><span style="color:#84CC16"> v2.1.0</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># Git checks out the midpoint</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># You test...</span></span>
<span class="line"><span style="color:#FDE68A">git</span><span style="color:#84CC16"> bisect</span><span style="color:#84CC16"> good</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># Git narrows the range, checks out next midpoint</span></span>
<span class="line"><span style="color:#FDE68A">git</span><span style="color:#84CC16"> bisect</span><span style="color:#84CC16"> bad</span></span>
<span class="line"><span style="color:#F59E0B;font-style:italic"># ...repeat until found</span></span></code></pre>
<p>There's something deeply satisfying about watching the range collapse. "423 revisions left to test." Then 211. Then 105. Each step cuts the problem in half with surgical precision. It's the closest programming gets to a magic trick.</p>
<h2>The Reveal</h2>
<p>After nine steps, <code>git bisect</code> pointed its finger at a commit from three weeks ago. The message read: "minor cleanup, no functional changes." I laughed, because of course it did. The commit reformatted a config file and, in the process, changed a tab to spaces inside a YAML block where indentation mattered. A silent, invisible, catastrophic change hiding behind an innocent commit message.</p>
<p>I fixed the indentation, verified the calculation, and ran <code>git bisect reset</code> to return to the present. Total time: twenty minutes. Without bisect, I'd still be reading commit diffs tomorrow. Sometimes the best tools are the ones you forget exist until you desperately need them.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[CSS Specificity Wars]]></title>
            <link>https://geeklife.in.ua/css-specificity-wars/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/css-specificity-wars/</guid>
            <pubDate>Sat, 22 Sep 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[In which a simple color change spirals into a deep dive through cascading stylesheets, specificity calculators, and the liberal use of !important.]]></description>
            <content:encoded><![CDATA[<p>It started, as these things always do, with a simple request. "Can you make that button blue?" Sure. One line of CSS. How hard can it be?</p>
<p>Forty-five minutes later, I'm three levels deep in a specificity calculator, my stylesheet has grown by two hundred lines, and the button is still stubbornly green. Somewhere in the depths of a third-party component library, a selector with the specificity of a small nation is overriding everything I write. The selector looks like someone rolled their face across a keyboard: <code>div.container > section.main-content .widget-wrapper:not(.disabled) button.btn.btn-primary</code>.</p>
<h2>The !important Temptation</h2>
<p>Every CSS developer knows the siren call of <code>!important</code>. It sits there in the specification, promising a quick fix, whispering sweet nothings about how "just this once" won't hurt. It's the duct tape of the web -- ugly, effective, and a sign that something has gone structurally wrong.</p>
<p>I've seen codebases where <code>!important</code> appears more often than semicolons. Where developers wage escalating wars of specificity, each one adding another <code>!important</code> to override the previous one, until the stylesheet reads like a shouting match between people who all believe they're right. And technically, they all are. That's the tragedy.</p>
<h2>Learning to Let Go</h2>
<p>The real lesson of CSS specificity isn't about selectors or cascading order. It's about humility. It's about accepting that a language designed to style documents in 1996 is now responsible for pixel-perfect responsive layouts across twelve screen sizes, and that maybe -- just maybe -- the abstractions we've built on top of it are the problem, not the solution.</p>
<p>I made the button blue eventually. I used a class with a single-level selector, removed three layers of unnecessary nesting, and deleted forty lines of dead CSS that nobody had touched since 2010. The fix was subtraction, not addition. As it usually is. But nobody writes blog posts about deleting code. It's not dramatic enough.</p>
<p>Except, apparently, I just did.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Debugging at 3 AM]]></title>
            <link>https://geeklife.in.ua/debugging-at-3am/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/debugging-at-3am/</guid>
            <pubDate>Thu, 15 Mar 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[There's a special kind of clarity that comes at 3 AM when it's just you, a bug, and the quiet hum of a monitor. Or maybe that's just sleep deprivation talking.]]></description>
            <content:encoded><![CDATA[<p>There's a special kind of clarity that comes at 3 AM. The office is empty, the Slack channels are silent, and the only sound is the quiet hum of your monitor and the occasional click of a mechanical keyboard. You've been chasing this bug for six hours. Your coffee went cold two hours ago. You don't care. You're close.</p>
<p>The bug is one of those beautiful, maddening creatures that only manifests in production, only on Tuesdays, and only when the user's name contains a Unicode character. You've narrowed it down to a string comparison deep inside a sorting function that someone wrote in 2009 and marked with a comment that reads "temporary fix." Three years later, it's load-bearing architecture.</p>
<h2>The Bargaining Phase</h2>
<p>At some point during a late-night debug session, you start making deals with the universe. "If I find this bug in the next ten minutes, I'll write proper tests for the entire module." You won't, of course. But the promise feels real in the moment, the way New Year's resolutions feel real on January 1st.</p>
<p>You add another <code>console.log</code>. Then another. Your terminal looks like the Matrix, except instead of falling green characters, it's an avalanche of <code>[DEBUG] value is: undefined</code>. You briefly consider adding a <code>console.log</code> inside the <code>console.log</code> to debug why the <code>console.log</code> isn't showing what you expect. This is the moment you should go to sleep. You add the log anyway.</p>
<h2>The Breakthrough</h2>
<p>And then it happens. At 3</p><div></div> AM, between your nineteenth coffee and your third existential crisis, you see it. A single character. A <code>=</code> where there should be <code>===</code>. The kind of bug that would take thirty seconds to find in a code review but six hours to find in the wild. You fix it, run the tests, and everything passes. You feel like a genius and an idiot simultaneously -- a state of being that, honestly, describes most of programming.<p></p>
<p>You commit the fix with a message that reads "fix string comparison" and go to bed, knowing full well that tomorrow someone will ask why such a simple fix took an entire evening. You'll shrug and say "it was tricky to reproduce." They'll nod. They've been there too.</p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[The Making of Fun Da Vinci]]></title>
            <link>https://geeklife.in.ua/fun-da-vinci/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/fun-da-vinci/</guid>
            <pubDate>Wed, 04 May 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Here's where it all started, and here's what we ended up with. We changed a lot, assembled a solid team, and gained experience selling a game. We'll share all of it with you.]]></description>
            <content:encoded><![CDATA[<p><a href="https://geeklife.in.ua/2010/07/27/make-game-in-nine-days/">Here's</a> where it all started, and <a href="http://armorgames.com/play/10964/fun-da-vinci">here's</a> what we ended up with. We changed a lot, assembled a solid team, and gained experience selling a game -- we'll share all of it with you, dear reader. Interested? Welcome aboard.</p>
<h2>Programming</h2>
<p>This part's simple -- me and Nikita Sidorenko (Division). People say two programmers on one simple Flash game is too many. I categorically disagree. In practice, if you write a game solo, you eventually give up. Maybe it's just us, but trust me, we tried -- working as a pair is both more fun and more productive. I explain it through guilt: if one person is working on something, the other feels ashamed for slacking and starts pitching in.</p>
<p><img src="/fun-da-vinci/images/fdv-1.webp" alt="Four art style iterations of the game: early cartoon prototype, item sprites, neon concept, and final Leonardo manuscript style" loading="lazy"></p>
<h2>Design</h2>
<p>Everything starts with design, and in a quality game it's front and center. As you know, I was the one who drew (read: borrowed) the art for the previous version. That flies for a contest, but for a real product you need proper, and most importantly, original art. Kherson is a small city -- everyone knows everyone -- and there aren't many good designers around, so we didn't have to search long for <a href="https://www.youtube.com/watch?v=K7r9dbqYrHs">Semyon Khramtsov, the "freelancer from the provinces"</a>.</p>
<p><em>"Even though I didn't particularly like puzzles, the guys' enthusiasm was infectious. First I proposed several art styles, including a stylization of Leonardo's manuscripts. That's the one everyone fell for. Sure, it's a pretty mainstream trick in graphic design, but the allure of synthesizing an interactive environment with that famous sketch style of the Renaissance mega-designer was too strong. I re-read his biography, googled all his works to mine as much flavor for the game as possible -- basically, immersed myself in the right atmosphere as deeply as I could. Of course, I consider the result an affront to his image and inventions, but who are we compared to the Master? Thank God I didn't repaint the game in bright 'casual' colors, as one of the judges suggested at the game-lynch -- we'd definitely be burning in hell for that. My biggest achievement in this project was diving into level design and combining the artist's grim self-portrait with the Mona Lisa's smile :)" -- Semyon Khramtsov</em></p>
<p><img src="/fun-da-vinci/images/fdv-2.jpg" alt="Da Vinci self-portrait evolution: original sketch, grayscale adaptation, and final game version with Mona Lisa smile" loading="lazy"></p>
<h2>Sound and Music</h2>
<p>The last but by no means least area -- the musical and sound design of the game. It doesn't just bring the game to life; it keeps the player from getting bored -- at least until it starts to annoy them. Semyon happened to know a very talented composer he'd been working with on small Flash projects for a while -- <a href="http://requix.promodj.ru/">Vladimir Marinichev</a> -- which was perfect timing.</p>
<p><em>"First things first, to immerse myself in the Renaissance atmosphere, I re-watched a biographical film about Leonardo da Vinci and listened to works by composers of that era. For the menu theme, I based it on a piece by Claude Gervaise that would convey the mood of a thoughtful Creator and transport the player to the Renaissance. I tried to keep the arrangement and instrumentation true to the style of that period.
For the gameplay sound design, many sounds were recorded using everyday objects. We decided to leave the levels without a musical score and instead convey the atmosphere of a workshop. During gameplay you can hear Leonardo constantly scribbling notes and humming something to himself, engrossed in yet another experiment.
Unfortunately, in trying to reduce the game's file size, we had to degrade the quality of all sounds and music, which of course made it harder to realistically convey all the nuances and details. But overall, many people were happy with the result, and it was very gratifying to receive a high rating from FGL." -- Vladimir Marinichev</em></p>
<p>And so, with the team assembled, off we went...</p>
<h2>The Beginning</h2>
<p>The first commit was on August 4, 2010 -- that's when you can mark the start of development. We slowly began fixing bugs, and since we all had day jobs, progress was very slow. We set up a task manager, created a pile of tasks, had lots of discussions -- all about nothing, really. In its final form, if you took all the tasks into account, the game should have looked like a mini Starcraft 2 with its own BattleNet (which had just come out at the time -- very distracting). The unrealistic scope of our plans was our first mistake. Don't build enormous castles. You'll abandon most of it once you realize your plans would have surprised even Napoleon. That's exactly what happened to us. We came to our senses when we decided to participate in FlashGamm KYIV 2010. We needed something to show, and we had nothing -- not even a working prototype. That's when the real work began. First we axed most of the tasks, minimized and optimized everything we could. And that produced results.</p>
<p><img src="/fun-da-vinci/images/fdv-4.webp" alt="Task manager screenshot: a to-do list including &#x22;Stop playing Starcraft 2&#x22; with humorous entries" loading="lazy"></p>
<h2>FlashGamm KYIV 2010</h2>
<p>By the start of the conference we'd managed to cobble together something resembling a prototype and build a few lackluster levels. With that, Semyon and Nikita headed to Kyiv. We competed in the Indie category, plus we signed up for the game-lynch (a public critique session) -- oh boy, did they tear us apart... But the game-lynch itself was incredibly useful. They pointed out our game's shortcomings, our mistakes, and gave us advice on where to go next. The encouraging part was that we'd already anticipated many of the flaws they called out. During the audience vote for best game-lynch game, ours scored exactly 0 points (zen mode) -- and after that, none of us expected to win anything at all. But out of nowhere, we won the "Future Hit" nomination! The joy was immense. I think this event had a really positive effect on the team's morale -- we realized there was something to this game and we needed to finish it no matter what.</p>
<p><img src="/fun-da-vinci/images/fdv-5.webp" alt="The Fun Da Vinci team receiving the Future Hit award at FlashGamm KYIV 2010 conference" loading="lazy"></p>
<h2>Wrapping Up</h2>
<p>After the conference, development settled into a steady rhythm and everything ran like clockwork. But there were annoying bugs. The first was objects falling through other objects -- a ghost that had haunted us since Ball Factory (the first prototype). We solved it pragmatically, by trial and error. I started tinkering with Box2D settings and found a quirk: changing <code>b2_aabbMultiplier</code> from 0.2 to 0.1 magically fixed the fall-through issue. The second bug crept up from where we least expected. Everywhere we needed a button, we used <code>SimpleButton</code>, and it never even crossed our minds that in newer Flash Player versions, button states would start "sticking" -- and there was no way to fix it no matter what hacks we tried. We had to build our own solution. Discussion on <a href="http://www.flasher.ru/forum/showthread.php?t=148860">flasher.ru</a>.</p>
<h2>Selling the Game</h2>
<p>Another thing happened at FlashGamm -- we met Stefan Keisch. We had no idea how to sell a game. There was some information online about FGL (Flash Game License), but it was all vague, and you needed to attract sponsors. That's exactly what Stefan does -- not for free, of course: 30% of the game's sale price. After some thought and deliberation, we accepted the offer. Stefan also gave recommendations on how to make the game more attractive to sponsors. The first thing he pointed out was the name. Our working title was "Balls Da Vinci" -- that's the name we competed under at FlashGamm. Unfortunately, the name triggered too many unwanted associations with Leonardo's manhood (which we found hilarious, admittedly), so we rebranded to "Fun Da Vinci" (maybe it's only a puzzle for us -- for da Vinci's brain, it's child's play).</p>
<p>The listing went up on FGL on January 14, 2011. FGL reviews every game submitted.</p>
<p><em>Review Information:</em></p>
<p>Intuitiveness: 7 Good
Fun: 6 Average
Graphics: 7 Good
Sound: 8 Great
Quality: 7 Good
Overall: 7 Good</p>
<p>Comments:
Standard execution of a traditional physics game with a Leonardo da Vinci theme
Creative level design, familiar game mechanics, polished interface
Lacks depth beyond first playthrough. Consider adding achievements or a scoring mechanism of some sort</p>
<p>Right away someone offered us $1,000, but we wanted more -- at least $3K. That bid sat there for about a month. We were starting to lose hope when suddenly several major portals began competing for the game (I suspect Stefan had something to do with it). Bids rose and fell. We noticed that sponsor activity picked up toward the end of each week. When the bid hit $3,000, we nearly caved and accepted, but a certain bald someone suggested we hold out -- right at that moment, we'd received an offer from <a href="http://jayisgames.com/">jayisgames.com</a> to write a <a href="http://jayisgames.com/archives/2011/03/fun_da_vinci.php">review</a> of the game. We were thrilled -- besides the "street cred" from a well-known portal, it could trigger a new wave of bids. And it did... After another round of sponsor bidding wars, the price settled at $5,600. We strangled our inner cheapskate and accepted. That was another victory. From listing on FGL to accepting the bid: exactly 2 months.</p>
<p>As you've probably guessed, Armor Games won. Sponsors usually ask you to brand the game for their portal -- add an intro, integrate their API, and so on. All easy enough, but we ran into problems with the Armor Games API, which absolutely refused to connect. We made a blank project -- worked perfectly there. In the actual game project -- nope. By some miracle, Nikita figured out that if you initialized the API in the preloader, everything was fine. Weird, but whatever.</p>
<h2>Splitting the Money</h2>
<p>So, you all want to know how much we made? Here's the breakdown:
FGL takes 10% for its services (half of which Stefan pays by agreement);
10% of 5,600 = 560 (FGL's cut);
560 / 2 = 280 (our share of FGL's fee);
30% of 5,600 = 1,680 (Stefan's cut);
Bottom line: 5,600 -- 280 -- 1,680 = 3,640.</p>
<p>Three and a half grand split four ways over four months -- not a bad result, for Nigerian guest workers. How to make a living from games alone? -- a question that puzzled us. But our enthusiasm only grew, and we're not giving up this hobby. We'll figure out the answer eventually -- maybe even in the comments to this post ;)</p>
<h2>Future Plans</h2>
<p>We're already working on a second version with new items, levels, and features. We have ideas for interesting game mechanics and unexplored themes. On the technical side, we're looking toward the App Store and Android Market, with Unity3D to help us get there.</p>
<p><img src="/fun-da-vinci/images/fdv-3.webp" alt="The Fun Da Vinci development team: Division (Lead Programmer), Titanum (Programmer), Xsem (Art and Design), Requix (Sound)" loading="lazy"></p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
        <item>
            <title><![CDATA[Making a Game in Nine Days -- Is That Even Possible?!]]></title>
            <link>https://geeklife.in.ua/ball-factory/</link>
            <guid isPermaLink="false">https://geeklife.in.ua/ball-factory/</guid>
            <pubDate>Tue, 27 Jul 2010 00:00:00 GMT</pubDate>
            <description><![CDATA[I'm not really the adventurous type, but sometimes it happens. And ten days ago, it happened. A buddy suggested I enter an IGDC game jam. Here are my impressions.]]></description>
            <content:encoded><![CDATA[<p>I'm not really the adventurous type, but sometimes it happens. And ten days ago, it happened. Exactly ten days ago, my buddy Nikita (who goes by Division) suggested I enter an IGDC game jam.
Here are my impressions...</p>
<h2>The Contest</h2>
<p>For those who don't know, IGDC is a community of people who enjoy making games. A contest is announced, rules are set, and off you go -- anyone can participate. The theme for this particular contest was genuinely interesting: Indirect Control. The idea is that the player has no direct control over the game process. You can only influence things indirectly.</p>
<p>I'd never entered one of these contests before, unlike Nikita, who had even placed in the top ranks. We decided to write in ActionScript 3, which I knew only vaguely. Why AS? Simply because Division had an engine built on AS that used the well-known Decorator pattern -- used it so heavily, in fact, that the engine itself was called Decorator.</p>
<p>When I heard "indirect," the famous puzzle game "The Incredible Machine" came to mind. I got the idea to make something similar but change the goal. In The Incredible Machine, the objective is to get a mechanism working. In our game, you'd make all the balls roll into a specific pipe using various boxes, planks, and -- of course -- physics. Luckily, the Decorator engine supported Box2D, though looking at the relative sizes of the engine and Box2D, it's hard to tell who's supporting whom. Anyway, physics sorted.</p>
<p>Design, hmm... neither I nor Division could draw at all. We decided to offer the honorary position of designer to an acquaintance of mine. He drew a couple of scenes, but then something came up, and we had to draw everything ourselves -- by "ourselves" I mean me.</p>
<p><img src="/ball-factory/images/bf-1.webp" alt="Ball Factory main menu with various sports balls and options: Start Game, Editor, About Us" loading="lazy"></p>
<p><img src="/ball-factory/images/bf-2.webp" alt="Ball Factory gameplay on level 2: wooden crates, planks, and pipes on a colorful hillside" loading="lazy"></p>
<h2>Development</h2>
<p>One key rule of the contest: you have to make the game in 9 days. So work kicked off at a lively pace. I was coming up with the concept, sketching out what everything should look like, while Division was preparing the engine. We used Dropbox for file sharing -- once again convinced that app is indispensable.</p>
<p>The engine prep took about three days and fell squarely on Division's shoulders. But for collaborative work we needed version control. The choice was between Git and SVN. We chose SVN, which we would later regret.</p>
<p>We got into a rhythm pretty quickly. Development happened at night. Since my AS experience left much to be desired, Division would explain over Skype what I was doing wrong. Skype was a huge help on this project, actually -- first, it made things more fun, and second, we could quickly coordinate our tasks and actions.</p>
<p>I fondly remember those sleepless nights. It was genuinely exciting to learn something new (AS) by doing it. I used to think you had to read a thick book before you could start writing in a new language -- how wrong I was. In those 4 days of coding, I learned more than I would from a month of reading a smart book. Sure, I lacked theoretical knowledge, but that was quickly fixed by the school of hard knocks...</p>
<p><img src="/ball-factory/images/bf-3.webp" alt="Ball Factory level 4 gameplay: triangular ramps, pipes, and ball counter showing caught and total balls" loading="lazy"></p>
<p><img src="/ball-factory/images/bf-4.webp" alt="Ball Factory level editor showing XML level data with entity definitions and coordinates" loading="lazy"></p>
<h2>The SVN Disaster</h2>
<p>Things went smoothly for about four days, but then Saturday came -- the turning point. We wrecked SVN. Here's how it went down... I pushed a new commit, Division was supposed to update his repo, but there was a conflict that needed manual resolution. Out of inexperience, Division clicked the wrong thing or pressed the wrong button, and wrecked one file. All his changes in that file were lost. And of course, stupidity never comes alone... In a move that was more dumb than inexperienced, I advised him to roll back to the previous revision, assuming he'd committed before that. Naturally, the previous revision wiped out an entire day's work... We were livid. The submission deadline was the next day, and we hadn't even built the levels yet -- and as if by Murphy's Law, the most complex and tangled part got erased.</p>
<p>Nothing to do about it -- Division had to restore everything. Morale was dented, but the payoff was worth it: we ended up with a solid level editor and a pretty good-looking game (you're welcome, that was my design work).</p>
<h2>The Disqualification and Redemption</h2>
<p>Murphy's Law, act two... According to contest rules, you could be a day late with a 30% penalty, but you had to notify the organizer. Of course we weren't going to make it, and we'd known it beforehand. In the relevant IGDC forum thread, Nikita had been voicing concerns and guesses that we wouldn't finish on time. We weren't sure yet, so we warned them more explicitly once we realized our earlier posts hadn't been taken as an official late notice. The organizer said "why so late?" but nothing more. We were drawing levels until about 3 AM (with work in 4 hours). We'd wanted to submit earlier, but as always, things never go the way you want. Realizing we were looking at the full 30% penalty, we went to sleep. During the day, in breaks between my day job, I kept tweaking levels, fixing issues, preparing the game for release. And then, like a bolt from the blue -- we get disqualified. For being late. I was honestly in shock. So much effort and soul poured in, it was really upsetting. But what can you do -- we packed the archive and sent it off.</p>
<p>Dragging myself home, I bought a bunch of junk food -- chips and cookies -- to at least console myself somehow. Got home and crashed without even reaching the cookies or chips. Around 10 PM, my wife wakes me up with great news -- the admin reversed the disqualification! The joy was beyond words. Special thanks to the admin! The world was right again... We're in the fight... We're competing... And we wait for the scores...</p>
<h2>Lessons Learned</h2>
<p>Lessons I took away:
-- Learn by doing.
-- Making games is fun.
-- To hell with SVN. Next project, we're using Git.</p>
<p><img src="/ball-factory/images/bf-5.webp" alt="Ball Factory level 3 gameplay: balls bouncing along a circular path with pipes and ramps" loading="lazy"></p>
<p><img src="/ball-factory/images/bf-6.webp" alt="Ball Factory level selection screen with 16 numbered levels in a green grid" loading="lazy"></p>]]></content:encoded>
            <author>Alex Kucherenko</author>
        </item>
    </channel>
</rss>