<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Changing Existing Software on Brian Faust</title><link>https://wimdy.works/series/changing-existing-software/</link><description>Recent content in Changing Existing Software on Brian Faust</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate><atom:link href="https://wimdy.works/series/changing-existing-software/rss.xml" rel="self" type="application/rss+xml"/><item><title>Stored Data Is a Caller You Cannot Find with Code Search</title><link>https://wimdy.works/thoughts/stored-data-is-a-caller-you-cannot-find-with-code-search/</link><pubDate>Sun, 01 Dec 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/stored-data-is-a-caller-you-cannot-find-with-code-search/</guid><description>&lt;p&gt;An enum migration looked mechanical. A package-backed value object became a
native PHP backed enum. The domain concept stayed the same, the new cases were
easy to search, and current code wrote the canonical values. It was exactly the
kind of refactor that looks finished after a clean source search.&lt;/p&gt;
&lt;p&gt;Then an older row loaded a lowercase value. The native enum expected the same
word with different casing and rejected it during hydration.&lt;/p&gt;</description></item><item><title>The Strangler Pattern Still Needs an Exit Plan</title><link>https://wimdy.works/thoughts/the-strangler-pattern-still-needs-an-exit-plan/</link><pubDate>Fri, 01 Nov 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/the-strangler-pattern-still-needs-an-exit-plan/</guid><description>&lt;p&gt;A replacement proof of concept imported one provider&amp;rsquo;s postal data into
PostgreSQL and served one existing JSON-RPC search method from a Go binary. A
checked-in request and response fixture preserved field names, nulls, empty
strings, ordering, and identifier shape. Import failure left the last committed
dataset intact.&lt;/p&gt;
&lt;p&gt;The slice deliberately excluded every other provider, the other RPC methods,
queues, schedulers, authentication, caching, operational interfaces, and
traffic cutover. That is a good proof of one vertical path. It is not a plan
for retiring the system around it, and pretending otherwise would make the POC
less useful rather than more impressive.&lt;/p&gt;</description></item><item><title>Deprecation Is a Migration, Not a Warning</title><link>https://wimdy.works/thoughts/deprecation-is-a-migration-not-a-warning/</link><pubDate>Tue, 01 Oct 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/deprecation-is-a-migration-not-a-warning/</guid><description>&lt;p&gt;A billing rewrite introduced durable export records before removing several
legacy queue jobs. Each new worker carried a record identifier. The export
type, payload snapshot, attempts, failure details, and retry state lived in the
database. A registry selected the current handler.&lt;/p&gt;
&lt;p&gt;That order matters. A queued object is not merely code waiting to run. Its
serialized class name and properties are data, even if nobody thinks of them as
a schema. Delete the class while an old payload remains, and PHP can deserialize
it as an incomplete object which no longer has the method the worker expects.&lt;/p&gt;</description></item><item><title>Static Analysis Turns Assumptions into Questions</title><link>https://wimdy.works/thoughts/static-analysis-turns-assumptions-into-questions/</link><pubDate>Sun, 01 Sep 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/static-analysis-turns-assumptions-into-questions/</guid><description>&lt;p&gt;A domain action caught any exception and returned &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, or an empty
array. Elsewhere, reusable services read the current request, tenant, and
locale from mutable framework context.&lt;/p&gt;
&lt;p&gt;Both patterns can be intentional. A fallback may be the contract at a
presentation boundary. Ambient context is normal in the code which receives a
web request. The problem appears when domain failure becomes indistinguishable
from a valid negative answer, or when a reusable service silently depends on
which request happened to call it. At that point the code still looks
convenient, but the contract has gone missing.&lt;/p&gt;</description></item><item><title>A Code Review Should Test the Decision, Not the Author</title><link>https://wimdy.works/thoughts/a-code-review-should-test-the-decision-not-the-author/</link><pubDate>Thu, 01 Aug 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-code-review-should-test-the-decision-not-the-author/</guid><description>&lt;p&gt;One booking flow could trigger the same automatic wallet refill twice. The
first refill happened while booking. A later debit event ran the ordinary
refill check again.&lt;/p&gt;
&lt;p&gt;The correction wrote a short-lived marker per account containing the
pending amount. When a matching shipment debit arrived, it consumed the marker
and skipped one refill. Focused tests covered the expected sequence.&lt;/p&gt;
&lt;p&gt;That is enough to begin a review, not finish one. A tidy helper and a green
happy-path test can make the change look settled. The decision is not “is this
cache helper elegant?” It is:&lt;/p&gt;</description></item><item><title>A Small Change Limits the States a Reviewer Must Hold</title><link>https://wimdy.works/thoughts/a-small-change-limits-the-states-a-reviewer-must-hold/</link><pubDate>Mon, 01 Jul 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-small-change-limits-the-states-a-reviewer-must-hold/</guid><description>&lt;p&gt;A table needed four columns, a self-referencing constraint, and an index. The
first migration added everything in one schema callback. That was concise, but
production could already contain any subset of those objects.&lt;/p&gt;
&lt;p&gt;The repair was longer, which made it look worse by the usual pull-request
metrics. Each column, constraint, and index became a separate guarded migration.
The constraint waited for its column. The index waited for both of its columns.
Rollback deliberately removed nothing because some schema objects might predate
the repair.&lt;/p&gt;</description></item><item><title>Technical Debt Is a Change Tax You Can Name</title><link>https://wimdy.works/thoughts/technical-debt-is-a-change-tax-you-can-name/</link><pubDate>Sat, 01 Jun 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/technical-debt-is-a-change-tax-you-can-name/</guid><description>&lt;p&gt;A compatibility change introduced a contract version and a measured
fallback lookup. Related work made acquisition atomic, separated expiration
from wall-clock dates, narrowed compatibility fields, enforced authorization,
and protected unselected drafts.&lt;/p&gt;
&lt;p&gt;That history proves several rules had to become explicit. It does not prove
that the old design charged technical-debt interest. The changes might be
planned slices, newly discovered requirements, corrections, or work a
different design would also need. A busy commit history can look expensive
without telling us what made it expensive.&lt;/p&gt;</description></item><item><title>A Rewrite Needs a Behavior Ledger</title><link>https://wimdy.works/thoughts/a-rewrite-needs-a-behavior-ledger/</link><pubDate>Wed, 01 May 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-rewrite-needs-a-behavior-ledger/</guid><description>&lt;p&gt;A desktop rewrite had screens for shipments, reports, settings, and logs. If we
had tracked the work as a feature checklist, all four could have been marked
complete and everyone could have gone home feeling productive.&lt;/p&gt;
&lt;p&gt;The old application also had behavior no screen list would reveal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a failed temporary-file cleanup must not turn a submitted print into a
failure, or the next run may print the label again;&lt;/li&gt;
&lt;li&gt;the first run treats older unprinted records differently from later runs;&lt;/li&gt;
&lt;li&gt;setup unlocks when a credential setting exists, not when its value is
non-empty; and&lt;/li&gt;
&lt;li&gt;a visible report search box does nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of those rules protect operators. One is an awkward contract. One is
probably a dead feature. Calling all of them “parity” hides the decision we
actually need to make. A rewrite needs a behavior ledger: a finite record of
what must survive, what will change deliberately, what may be dropped, and what
evidence closes each decision.&lt;/p&gt;</description></item><item><title>Parallel Implementations Are Not a Replacement Seam</title><link>https://wimdy.works/thoughts/parallel-implementations-are-not-a-replacement-seam/</link><pubDate>Mon, 01 Apr 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/parallel-implementations-are-not-a-replacement-seam/</guid><description>&lt;p&gt;An old shipping integration and its proposed replacement lived beside each
other. Both had an interface, a driver, request builders, and tests. From the
directory tree, that looked like a seam.&lt;/p&gt;
&lt;p&gt;When the replacement was finally made canonical, the change still crossed
dozens of files. The old interface disappeared, the new one moved, the
canonical driver changed its pickup and shipment flows, and a parallel driver
tree was deleted. Follow-up work then corrected a request shape and added
serialization tests.&lt;/p&gt;</description></item><item><title>A Characterization Test Is Not a Product Decision</title><link>https://wimdy.works/thoughts/a-characterization-test-is-not-a-product-decision/</link><pubDate>Fri, 01 Mar 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-characterization-test-is-not-a-product-decision/</guid><description>&lt;p&gt;A label setting behaved differently in test and production. Some customers
had come to depend on the difference even though the product already let them
choose the label size they wanted.&lt;/p&gt;
&lt;p&gt;A characterization test could record both defaults. It could tell us which
callers would notice if we made them equal. It could not answer the important
question: should the difference survive?&lt;/p&gt;
&lt;p&gt;Keeping it would protect customers who had never configured the setting.
Removing it would give every environment one rule and make the customer&amp;rsquo;s
choice explicit. The same observation supported arguments for either response.
Only one response matched the product we wanted to operate.&lt;/p&gt;</description></item><item><title>A Characterization Suite Is Scoped to a Change</title><link>https://wimdy.works/thoughts/a-characterization-suite-is-scoped-to-a-change/</link><pubDate>Fri, 01 Feb 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-characterization-suite-is-scoped-to-a-change/</guid><description>&lt;p&gt;A booking endpoint had more than thirty characterization cases. They covered
successful bookings, validation failures, wallet rules, locks, pending work,
email routing, and several provider response shapes.&lt;/p&gt;
&lt;p&gt;The next refactor removed one ambient HTTP dependency from the booking
services. The suite looked broad enough. It was not broad in the direction of
the change.&lt;/p&gt;
&lt;p&gt;One service read the global request to persist the original payload. Replacing
that read with the existing booking data object would keep ordinary responses
green while silently storing normalized values where operators expected the
raw input. The suite already protected a test-mode short circuit. It needed a
new case for the two representations.&lt;/p&gt;</description></item><item><title>A Directory Map Is Not a Redesign Plan</title><link>https://wimdy.works/thoughts/a-directory-map-is-not-a-redesign-plan/</link><pubDate>Tue, 01 Jan 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-directory-map-is-not-a-redesign-plan/</guid><description>&lt;p&gt;A reorganization plan found dozens of domain directories and several older
modules. It assigned them to a set of proposed bounded contexts, selected an
apparently isolated location-and-address context as a safe starting point, and
described where its files should move.&lt;/p&gt;
&lt;p&gt;The map was detailed and tidy. It was also missing the part which determined
whether the boundary was real.&lt;/p&gt;
&lt;p&gt;In the running system, the legacy application kept service-point policy while
calling separate location and postal applications. Location lookup called
postal lookup. Each application owned different state. Booking, validation,
labels, returns, settings, administration, and health checks all consumed some
part of the flow.&lt;/p&gt;</description></item></channel></rss>