<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Valentine Shi</title>
    <description>The latest articles on DEV Community by Valentine Shi (@valentineshi-dev).</description>
    <link>https://dev.to/valentineshi-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4065682%2Ffd2b4572-3f93-47d7-ac1a-e2ea74ea10b0.png</url>
      <title>DEV Community: Valentine Shi</title>
      <link>https://dev.to/valentineshi-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/valentineshi-dev"/>
    <language>en</language>
    <item>
      <title>Jev vs OpenAI Decisions API: A Follow Up</title>
      <dc:creator>Valentine Shi</dc:creator>
      <pubDate>Mon, 12 Oct 2026 08:12:27 +0000</pubDate>
      <link>https://dev.to/valentineshi-dev/jev-vs-openai-decisions-api-a-follow-up-3k92</link>
      <guid>https://dev.to/valentineshi-dev/jev-vs-openai-decisions-api-a-follow-up-3k92</guid>
      <description>&lt;p&gt;In my recent &lt;a href="https://valentineshi.dev/content/writings/Sdpfd6y8R2SzEZSlmu4AXg/jev-by-typesafe-ai-hype-tool-or-threat" rel="noopener noreferrer"&gt;article&lt;/a&gt; about Jev by TypeSafe AI, I questioned whether its speed, cost, and confidence scores offered enough advantages over conventional LLMs with Structured Outputs.&lt;/p&gt;

&lt;p&gt;And here is an interesting development.&lt;/p&gt;

&lt;p&gt;On October 6, OpenAI announced its Decisions API became available in public beta (see the announcement and the API doc links below).&lt;/p&gt;

&lt;p&gt;Powered by GPT-6 Luna, it provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typed decisions: predicates, predefined choices, and numeric scores.&lt;/li&gt;
&lt;li&gt;Probability and confidence estimates.&lt;/li&gt;
&lt;li&gt;Text and image inputs.&lt;/li&gt;
&lt;li&gt;Claimed ~10× faster responses than the Responses API.&lt;/li&gt;
&lt;li&gt;$0.10 per million input tokens, with no output-token charges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The functionality is remarkably similar to Jev. OpenAI now offers a specialized API for essentially the same tasks, with comparable pricing.&lt;/p&gt;

&lt;p&gt;I would be interested to see independent benchmarks comparing the two APIs, particularly on accuracy, latency, cost, and confidence calibration.&lt;/p&gt;

&lt;p&gt;And this development supports my original conclusion about Jev. We already had much of this functionality with Structured Outputs. Specialized decision APIs may make it faster and cheaper, especially at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing, High Load, SLA
&lt;/h3&gt;

&lt;p&gt;Let's start with pricing. Jev costs $0.042 per million input tokens, while OpenAI Decisions API costs $0.10. So Jev is about &lt;strong&gt;2.4 times cheaper&lt;/strong&gt;. Neither charges for output tokens.&lt;/p&gt;

&lt;p&gt;Looking at the upper capacity limits, Jev supports 6 million input tokens per minute, compared with up to 180 million for GPT-6 Luna. That's a 30-fold difference, although the OpenAI figure has yet to be confirmed for Decisions API.&lt;/p&gt;

&lt;p&gt;On the infrastructure side, Jev is still expanding its GPU capacity, and its limits may change. OpenAI already offers enterprise capacity reservations, so they may be available for Decisions API.&lt;/p&gt;

&lt;p&gt;Finaly, Jev has a clear pricing advantage, while OpenAI appears better positioned for high-load workloads and potentially better SLA. Whether the price advantage is sufficient vs OpenAI's solutions remains an open question. OpenAI definitely has greater bargaining power, capital, and infrastructure resources. If the niche is attractive, competitors will need more advantages than just pricing to survive in it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.openai.com/t/decisions-api-is-now-available-in-public-beta/1403877" rel="noopener noreferrer"&gt;Decisions API is now available in Public Beta&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.openai.com/api/docs/guides/decisions" rel="noopener noreferrer"&gt;The API Official Docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Git Submodules: When and How</title>
      <dc:creator>Valentine Shi</dc:creator>
      <pubDate>Mon, 05 Oct 2026 06:52:03 +0000</pubDate>
      <link>https://dev.to/valentineshi-dev/git-submodules-when-and-how-1o93</link>
      <guid>https://dev.to/valentineshi-dev/git-submodules-when-and-how-1o93</guid>
      <description>&lt;p&gt;&lt;strong&gt;Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Cases&lt;/li&gt;
&lt;li&gt;Submodules, Subtrees, Mono- or Multirepo?&lt;/li&gt;
&lt;li&gt;
A Practical Setup Example

&lt;ul&gt;
&lt;li&gt;fork.dev&lt;/li&gt;
&lt;li&gt;Terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Daily Workflow&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Git &lt;a href="https://git-scm.com/docs/git-submodule" rel="noopener noreferrer"&gt;submodules&lt;/a&gt; are a built-in Git mechanism for composing repositories at fixed commits. Unlike a monorepo, separate repositories, or worktrees, they let a root repository record exact revisions of independently owned submodules.&lt;/p&gt;

&lt;p&gt;The basic idea is simple: the root repository records a nested repository at an exact commit. The root repository stores the path and remote URL in &lt;code&gt;.gitmodules&lt;/code&gt;, and records the submodule commit as a &lt;code&gt;gitlink&lt;/code&gt;. A clone can later reconstruct the same combination of repositories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;Briefly: use a submodule when a root repository must record an approved revision of code maintained elsewhere. Here are some useful applications of submodules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep private documentation and deployment configuration in the root repository, with public source code in a submodule.&lt;/li&gt;
&lt;li&gt;Pin the exact revision of a shared library maintained and released separately.&lt;/li&gt;
&lt;li&gt;Record the approved revisions of independently released services or tools.&lt;/li&gt;
&lt;li&gt;Pin a reviewed commit from a maintained fork of an external project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In product development, the root repository represents the product release context: its documentation, configuration, integration decisions, and approved component revisions. Each submodule represents a component with its own codebase, maintainers, and delivery cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submodules, Subtrees, Mono- or Multirepo?
&lt;/h2&gt;

&lt;p&gt;Here are some key distinctions, briefly.&lt;/p&gt;

&lt;p&gt;A submodule keeps source independent while the root repository records the exact revision used by the product. It fits when public source and private product material must remain separate but form one reproducible product revision.&lt;/p&gt;

&lt;p&gt;A subtree copies external source into the root repository. Use it when the root repository should own that source.&lt;/p&gt;

&lt;p&gt;A monorepo works well when code, access rules, and release cadence belong together. &lt;a href="https://nx.dev/" rel="noopener noreferrer"&gt;Nx&lt;/a&gt; and similar monorepo tools allow for task execution, caching, project boundaries, and releases conveniently from the single root repository.&lt;/p&gt;

&lt;p&gt;A multirepo gives each component its own repository, ownership, and history, but does not by itself record which revisions belong together.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Setup Example
&lt;/h2&gt;

&lt;p&gt;Consider a product with private documentation and deployment configuration, and public source code maintained in a separate repository.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Root repository:&lt;/strong&gt; private; contains private material and one or more submodule references.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Submodule:&lt;/strong&gt; public; contains source code and is its own source of truth.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root/
├── .gitmodules
├── docs/                    # private specifications and operational material
├── deployment/              # private deployment configuration
└── submodule/               # separately owned source repository
    ├── README.md
    └── src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root repository tracks &lt;code&gt;.gitmodules&lt;/code&gt; and the exact commit recorded at &lt;code&gt;submodule/&lt;/code&gt;. The files inside &lt;code&gt;submodule/&lt;/code&gt; belong to the submodule repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  fork.dev
&lt;/h3&gt;

&lt;p&gt;I use here the &lt;a href="https://fork.dev/" rel="noopener noreferrer"&gt;Fork&lt;/a&gt; Git client. The setup via terminal is in the next section.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the local folder structure:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;mkdir &lt;/span&gt;root
   &lt;span class="nb"&gt;cd &lt;/span&gt;root
   &lt;span class="nb"&gt;mkdir &lt;/span&gt;docs deployment submodule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In Fork, create a Git repository in &lt;code&gt;root/&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open &lt;code&gt;submodule/&lt;/code&gt; in Fork and create a separate Git repository there. Add an initial file such as &lt;code&gt;README.md&lt;/code&gt;, commit it, and keep this repository independent from &lt;code&gt;root/&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create two empty repositories in your Git hosting service, for example: private: &lt;code&gt;https://github.com/example/root.git&lt;/code&gt;, public: &lt;code&gt;https://github.com/example/submodule.git&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In Fork, add &lt;code&gt;https://github.com/example/root.git&lt;/code&gt; as the &lt;code&gt;origin&lt;/code&gt; remote of the &lt;code&gt;root/&lt;/code&gt; repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add &lt;code&gt;https://github.com/example/submodule.git&lt;/code&gt; as the &lt;code&gt;origin&lt;/code&gt; remote of the &lt;code&gt;submodule/&lt;/code&gt; repository, then push its initial commit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return to the &lt;code&gt;root/&lt;/code&gt; repository. In the sidebar, right-click &lt;strong&gt;Submodules&lt;/strong&gt; → &lt;strong&gt;Add New Submodule&lt;/strong&gt;. Enter repository URL: &lt;code&gt;https://github.com/example/submodule.git&lt;/code&gt;, local path: &lt;code&gt;submodule&lt;/code&gt;. Fork registers the existing local repository as the submodule.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commit &lt;code&gt;.gitmodules&lt;/code&gt; and the new &lt;code&gt;submodule&lt;/code&gt; gitlink in the root repository, then push the root repository to its private &lt;code&gt;origin&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Terminal
&lt;/h3&gt;

&lt;p&gt;The same setup from a terminal, creating both repositories locally first and then attaching their remotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the local folder structure.&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;root
&lt;span class="nb"&gt;cd &lt;/span&gt;root
&lt;span class="nb"&gt;mkdir &lt;/span&gt;docs deployment submodule

&lt;span class="c"&gt;# Initialize the private root repository.&lt;/span&gt;
git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
git remote add origin https://github.com/example/root.git

&lt;span class="c"&gt;# Initialize the public submodule repository.&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;submodule
git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# Submodule"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; README.md
git add README.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit"&lt;/span&gt;
git remote add origin https://github.com/example/submodule.git
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main

&lt;span class="c"&gt;# Return to the root repository and register the existing local repository&lt;/span&gt;
&lt;span class="c"&gt;# as a submodule.&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
git submodule add &lt;span class="nt"&gt;--force&lt;/span&gt; https://github.com/example/submodule.git submodule

&lt;span class="c"&gt;# Commit the root repository state.&lt;/span&gt;
git add docs deployment .gitmodules submodule
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add submodule"&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Daily Workflow
&lt;/h2&gt;

&lt;p&gt;Treat the submodule commit as a dependency lock. Change code in the submodule repository, then deliberately update the root repository's pointer.&lt;/p&gt;

&lt;p&gt;With that workflow, the repositories keep their separate ownership, history, and visibility, while the root repository still records the exact combination that belongs to the system.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Doble-click on Submodules-&amp;gt;submodule repository to open its tab. Add its source files, stage them, commit them, and push them to the public &lt;code&gt;origin&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return to the root repository. Add private files under &lt;code&gt;docs/&lt;/code&gt; and &lt;code&gt;deployment/&lt;/code&gt;. Stage &lt;code&gt;docs/&lt;/code&gt;, &lt;code&gt;deployment/&lt;/code&gt;, commit them, and push them to the private &lt;code&gt;origin&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For private documentation or configuration changes, work in the root repository directly. For source changes, commit and push them in the submodule first, then commit the changed submodule reference in the root repository.&lt;/p&gt;

&lt;p&gt;When cloning later, use recursive initialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--recurse-submodules&lt;/span&gt; &amp;lt;root-repository-url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an existing clone, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git submodule update &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;References: &lt;a href="https://git-scm.com/docs/git-submodule" rel="noopener noreferrer"&gt;Git submodule documentation&lt;/a&gt; and &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules" rel="noopener noreferrer"&gt;Git submodules guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>git</category>
      <category>development</category>
    </item>
    <item>
      <title>AI Layoffs Seem To Be Reversed</title>
      <dc:creator>Valentine Shi</dc:creator>
      <pubDate>Tue, 29 Sep 2026 05:11:00 +0000</pubDate>
      <link>https://dev.to/valentineshi-dev/ai-layoffs-seem-to-be-reversed-4jgl</link>
      <guid>https://dev.to/valentineshi-dev/ai-layoffs-seem-to-be-reversed-4jgl</guid>
      <description>&lt;p&gt;The economics of replacing people with AI are proving more complicated than expected. Two recent studies provide some interesting evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. KPMG: The cost vs value of AI are becoming a serious challenge.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;KPMG's Global AI Pulse Q2 2026 (surveyed 2,145 senior executives across 20 countries) found that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;29% report difficulties understanding and controlling AI operating costs.&lt;/li&gt;
&lt;li&gt;33% identify limited understanding of AI costs as a challenge to deploying AI agents.&lt;/li&gt;
&lt;li&gt;49% have delayed, paused, narrowed, or scaled back AI agent deployments because expected costs began to outweigh anticipated value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Careerminds: Companies are already rehiring after AI-related layoffs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In February 2026, Careerminds surveyed 600 HR professionals from organizations that had conducted layoffs in the preceding 12 months. More than 75% (approximately 450–460) reported AI-related layoffs. Out of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;32.7% reported rehiring for 25–50% of eliminated roles.&lt;/li&gt;
&lt;li&gt;35.6% reported rehiring for more than half of eliminated roles.&lt;/li&gt;
&lt;li&gt;Among organizations that rehired, 52.1% reported doing so within six months.&lt;/li&gt;
&lt;li&gt;30.9% reported that rehiring costs exceeded the savings achieved through the original layoffs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another interesting finding: 54.6% reported that &lt;em&gt;AI required more human oversight than anticipated, making the layoffs not worth it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the evidence does establish is that AI replacement strategies have already failed economically or operationally in a considerable amount of organizations, resulting in reduced AI deployment, revised workforce decisions, and renewed hiring.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;p&gt;KPMG — &lt;a href="https://assets.kpmg.com/content/dam/kpmgsites/xx/pdf/2026/06/global-ai-pulse-q2.pdf?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Global AI Pulse Q2 2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Careerminds — &lt;a href="https://careerminds.com/blog/cost-of-ai-layoffs?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;AI-led layoffs: What HR leaders wish they knew before making job cuts&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hiring</category>
    </item>
    <item>
      <title>Jev by TypeSafe AI: Hype, Tool or Threat?</title>
      <dc:creator>Valentine Shi</dc:creator>
      <pubDate>Mon, 21 Sep 2026 09:47:13 +0000</pubDate>
      <link>https://dev.to/valentineshi-dev/jev-by-typesafe-ai-hype-tool-or-threat-h26</link>
      <guid>https://dev.to/valentineshi-dev/jev-by-typesafe-ai-hype-tool-or-threat-h26</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: Backends in Node.js and TypeScript&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A few days ago, TypeSafe AI introduced &lt;a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev" rel="noopener noreferrer"&gt;Jev&lt;/a&gt;, a new model designed to make fast, structured decisions that software can use directly.&lt;/p&gt;

&lt;p&gt;I read the announcement and had two questions: Is Jev more of a hype? Is it really useful for me as a backend engineer? And does it present a new threat of replacing me as a software engineer? Jev also reminded me of the OpenAI REST API "structured outputs" endpoints. I wondered whether Jev would offer any advantages over OpenAI.&lt;/p&gt;

&lt;p&gt;Here is what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does Jev Actually Do
&lt;/h2&gt;

&lt;p&gt;Jev (by TypeSafe AI) belongs to a class of narrowly specialized models that provide structured outputs rather than just prose. It accepts application state as input and returns structured decisions. The possible outputs and their types are defined in advance.&lt;/p&gt;

&lt;p&gt;For example, an application could provide information about a customer and ask Jev to classify the customer's churn risk, determine which support department should handle a request, or score a potential business opportunity. The application receives predefined values that it can use in its business logic.&lt;/p&gt;

&lt;p&gt;Very similar to OpenAI "structured outputs", I think.&lt;/p&gt;

&lt;p&gt;TypeSafe AI claims that Jev provides several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Response times of 70–500 ms.&lt;/li&gt;
&lt;li&gt;Input costs of $0.042 per million tokens.&lt;/li&gt;
&lt;li&gt;Guaranteed adherence to predefined output types.&lt;/li&gt;
&lt;li&gt;Calibrated probabilities and confidence scores accompanying its decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The company also claims substantial improvements in speed and cost compared with existing LLMs performing similar tasks.&lt;/p&gt;

&lt;p&gt;These are the company's published results. Jev is currently in early access, and I haven't tested it myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jev vs OpenAI Structured Outputs
&lt;/h2&gt;

&lt;p&gt;I have already worked with OpenAI's API to implement structured classification and decision-making workflows. OpenAI introduced Structured Outputs in August 2024. With &lt;code&gt;strict: true&lt;/code&gt;, developers can define a JSON Schema and receive responses conforming to that schema.&lt;/p&gt;

&lt;p&gt;We can already classify incoming data, select values from predefined enums, extract structured information, and use the results in application code. ([OpenAI][2]). Jev provides essentially the same capabilities for these use cases.&lt;/p&gt;

&lt;p&gt;Jev's underlying generation mechanism (it generates its decision outputs in parallel and provides associated probabilities - confidence scores) is interesting, but there is little reason to judge Jev's advantage based on its implementation details.&lt;/p&gt;

&lt;p&gt;The practical question for a backend engineer is whether Jev produces decisions of comparable accuracy, faster and at a lower cost, for the same workload. They claim it has an edge in this. But with OpenAI Structured Outputs we can select the seriously cheaper model still very capable of producing structured outputs with a large focused (cached) context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jev: Useful in Real Backend Systems?
&lt;/h2&gt;

&lt;p&gt;At one innovation analytics startup, I worked on backend architecture and LLM-powered ingestion and scoring pipelines. The system needed to process incoming information, classify it using controlled vocabularies, and produce structured results that could be used by subsequent application components.&lt;/p&gt;

&lt;p&gt;I implemented deterministic validation gates to check model outputs before allowing the code to proceed through the workflow. Looking at Jev's capabilities, I can see where it could fit into this architecture. For example, I could use it to classify incoming records or calculate particular scores.&lt;/p&gt;

&lt;p&gt;I had great results with &lt;a href="https://developers.openai.com/api/docs/models/gpt-4o-mini" rel="noopener noreferrer"&gt;GPT-4o Mini&lt;/a&gt; (quite cheaper than GPT Luna that TypeSafe AI compares Jev with) with latencies well within 100ms and costs like $0.15 per 1,000 requests of ~2K prompt and ~700 bytes JSON response.&lt;/p&gt;

&lt;p&gt;Separately, Jev's confidence scores may seem to be useful for workflows where an application needs to decide whether to accept a model's decision or send the case for further processing. But these scores can be equally misleading requiring additional deterministic validation gates, thus adding the complexity to a workflow.&lt;/p&gt;

&lt;p&gt;My conclusion is Jev deserves some A/B testing under realistic production workloads vs OpenAI structured outputs before any decision. With millions of requests it may offer an advantage. As well its confidence scores may be used in some cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Tool Or an Engineer's Replacement.
&lt;/h2&gt;

&lt;p&gt;This was my original concern. A usual senior backend engineer's work involves designing application architecture, domain models and service boundaries, implementing APIs, working with databases, and building distributed systems, and often, implementing business workflows that involve classification, scoring, and other decisions based on incoming data.&lt;/p&gt;

&lt;p&gt;Jev could automate some decision-making operations. As well as OpenAI structured outputs does. But hardly presents any threat to the entire engineering process. So the answer is "a tool", that has had alternatives for already ~2 years.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Conclusion
&lt;/h2&gt;

&lt;p&gt;After examining Jev and comparing it with OpenAI's existing Structured Outputs, I see it as a potentially useful addition to my backend engineering toolkit with advantages that have yet to be proven for production systems already using OpenAI structured outputs.&lt;/p&gt;

&lt;p&gt;For now, I am interested in seeing how Jev performs on real business workloads and whether its claimed performance improvements justify replacing existing LLM calls in production systems.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>backend</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>I Enjoy Software Development Even More With AI</title>
      <dc:creator>Valentine Shi</dc:creator>
      <pubDate>Thu, 20 Aug 2026 06:56:24 +0000</pubDate>
      <link>https://dev.to/valentineshi-dev/i-enjoy-software-development-even-more-with-ai-51oc</link>
      <guid>https://dev.to/valentineshi-dev/i-enjoy-software-development-even-more-with-ai-51oc</guid>
      <description>&lt;p&gt;I keep reading that AI turns an engineer into a reviewer of generated code and removes the enjoyable part of the work. My experience with Codex is quite opposite (will try Claude one day). I have always enjoyed software engineering, and now I enjoy it even more.&lt;/p&gt;

&lt;p&gt;Before I get to TDD and implementation, I use the agent to analyse the problem and technical domains and model the system in detail. Most of my design decisions happen there.&lt;/p&gt;

&lt;p&gt;I can examine more alternatives and find missing decisions earlier. However, to judge the output, I have to know beforehand how and why the system should work, and how the test and code should look like. The agent makes the analysis and modelling many times faster and helps me update the entire model while the design changes.&lt;/p&gt;

&lt;p&gt;The largest difference appears after a redesign. Updating diagrams and specifications and then reworking the code and tests used to consume a lot of time. Now it happens like 50 times faster (gut feeling). So I can redesign more freely and do not have to keep a weaker decision just because changing everything around it would take too long.&lt;/p&gt;

&lt;p&gt;The agent also does much of the routine work of propagating my decisions through specifications, code and tests. This allows me to spend much more time analyzing the domain and designing the system.&lt;/p&gt;

&lt;p&gt;Pure joy.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>AI in Development: Speed vs. Hidden Defects</title>
      <dc:creator>Valentine Shi</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:14:06 +0000</pubDate>
      <link>https://dev.to/valentineshi-dev/ai-in-development-speed-vs-hidden-defects-5cjg</link>
      <guid>https://dev.to/valentineshi-dev/ai-in-development-speed-vs-hidden-defects-5cjg</guid>
      <description>&lt;p&gt;The article &lt;a href="https://calnewport.com/on-ai-coding-and-its-discontents/" rel="noopener noreferrer"&gt;linked here&lt;/a&gt; describes a telling case: a senior developer discovers that Claude Code reduced the time needed to complete a task from a week to just two days. However, later on, the generated functionality caused failures in production twice. At first glance, the code looked plausible, but it contained hard‑to‑detect errors, and it turned out to be significantly more difficult to thoroughly verify AI‑generated implementation than one’s own.&lt;/p&gt;

&lt;p&gt;The code looked plausible, but identifying subtle defects was challenging because the developer hadn’t built a sufficient mental model of code they didn’t write themselves.&lt;/p&gt;

&lt;p&gt;I keep coming to the same conclusions. AI requires a clear engineering understanding of what the code should look like. Speeding things up without having a full mental model of the system leads to costly production errors and rework.&lt;/p&gt;

&lt;p&gt;I’m curious&amp;nbsp;—&amp;nbsp;are there any seriously experienced developers here who intensively use AI for production development, especially in unfamiliar, complex domains or when implementing major new functionality?&lt;/p&gt;

&lt;p&gt;How successfully are you able to delegate implementation to AI while still truly understanding, verifying, and being fully accountable for the result?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>coding</category>
      <category>debugging</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Real-Life Refactoring Example: ~3x Less Code to Read</title>
      <dc:creator>Valentine Shi</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:24:24 +0000</pubDate>
      <link>https://dev.to/valentineshi-dev/real-life-refactoring-example-3x-less-code-to-read-3mdl</link>
      <guid>https://dev.to/valentineshi-dev/real-life-refactoring-example-3x-less-code-to-read-3mdl</guid>
      <description>&lt;p&gt;There is a popular idea that refactoring is making code shorter. It is not entirely wrong. Repetition, pointless conditional branches, and dead variables do make code longer for no reason. But the usual topmost problems are when the intentions of a class's main public method, the business or operational ones, are unclear to a reader.&lt;/p&gt;




&lt;p&gt;I had such a case in an S3 document-upload adapter.&lt;/p&gt;

&lt;p&gt;The adapter was not doing anything unusual. It had to stream a document to S3-compatible storage, count actual bytes, calculate a checksum, reject empty or too-large content, normalize S3 errors, clean up a stored object where needed, and write technical logs. This is a substantial piece of work. The problem was that the public &lt;code&gt;store()&lt;/code&gt; method contained most of it directly.&lt;/p&gt;

&lt;p&gt;At a glance it looked like a method that stores a document. In practice, reading it meant walking through AWS SDK setup, &lt;code&gt;try/catch&lt;/code&gt; blocks, S3 error-object construction, cleanup, and a large logging payload before reaching the returned value.&lt;/p&gt;

&lt;p&gt;The code was not unreadable because it had many lines. It was unreadable because the important lines had no space to stand out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Main Method Refactor
&lt;/h2&gt;

&lt;p&gt;The main method did not show the adapter's use case. Before the refactoring, upload execution was part of &lt;code&gt;store()&lt;/code&gt; itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;s3_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documentStorageKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;countedStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mime_type&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;leavePartsOnError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
 &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// Detect validation errors.&lt;/span&gt;
 &lt;span class="c1"&gt;// Build an S3 error object.&lt;/span&gt;
 &lt;span class="c1"&gt;// Build an application exception.&lt;/span&gt;
 &lt;span class="c1"&gt;// Log it.&lt;/span&gt;
 &lt;span class="c1"&gt;// Throw it.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How do I decide what to refactor? The rule of thumb for production code is to watch the nesting depth. In my book, a class should not contain more than three levels of nesting. The first is the class declaration, the second is the method level, and the third is at most one level of nesting within a method - inline object literals and deeply nested objects are not allowed.&lt;/p&gt;

&lt;p&gt;The second rule - the main public methods must reveal their intention clearly by their names and their internal code must reveal the story of how the method does what it does in the domain or integration terms.&lt;/p&gt;

&lt;p&gt;So the first refactoring was to extract that &lt;code&gt;try/catch&lt;/code&gt; into a story-telling method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uploadToStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;documentStorageKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;countedStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was "Extract Method" refactoring, described by Martin Fowler in his Refactoring book. The main method started to read as a story because the implementation was hidden and the method name revealed intention. It packed 40+ lines into one intention-revealing call — roughly 40x less implementation detail to scan in the main flow.&lt;/p&gt;

&lt;p&gt;I used the same approach to extract two conditionals converting a raw S3 error into an application error, and to delete an empty object and throw another error. The first turned ~20 lines into one call, and the second reduced another ~30 lines to one call. Together, roughly 90 lines of implementation detail became three intention-revealing calls — about 30x less code to scan at this level. And look at the method names — they continue the story&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throwIfS3Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedObjectKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;httpStatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throwIfUploadedFileIsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedObjectKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;countedStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moreover, now a reader can answer the first useful question quickly: what happens when a document is stored? It is uploaded. The response is checked. Empty content and errors are dealt with.&lt;/p&gt;

&lt;p&gt;The helper methods still contain difficult code. That is fine. Difficult code should exist where it belongs. It should not hide the business flow of the public method.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!INFO]&lt;br&gt;
The main &lt;code&gt;store()&lt;/code&gt; method shrank from 121 lines to 39 — a 68% or 3x reduction in code to read.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A Private Helper Is Not Automatically a Public API
&lt;/h2&gt;

&lt;p&gt;The next request looked small. The upload service needed to delete the object it had just stored when a duplicate document was found, or when persistence failed later in the transaction.&lt;/p&gt;

&lt;p&gt;There was already a private cleanup method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;cleanupStoredObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentUUID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;documentStorageKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tempting change was to make it public. That would have been wrong.&lt;/p&gt;

&lt;p&gt;The private method accepted a document UUID, a storage key, and a timestamp. The timestamp existed only because the method wrote a cleanup-failure log. It also swallowed a deletion error. That was correct for the empty-upload path: the caller must receive the original “empty upload” error even when cleanup itself fails.&lt;/p&gt;

&lt;p&gt;It is not a public removal contract. The public operation became this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedObjectKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It now owns the S3 request. It owns the timing of the S3 operation. It normalizes the provider error. It writes the technical storage event. It throws the normalized error to its caller and it is universally used by the adapter and its caller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedObjectKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;removalStartedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;s3_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DeleteObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;storedObjectKey&lt;/span&gt;
        &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalizedFailure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalizeStorageFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logStorageEvent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ELogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;eventName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EDocumentStorageLogEventName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DOCUMENT_STORAGE_CLEANUP_FAILED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ELogOutcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;operationName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DOCUMENT_STORAGE_LOG_CONSTANTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;storedObjectKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;removalStartedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;normalizedFailure&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;normalizedFailure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caller decides what to do with that error.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;remove()&lt;/code&gt; itself catches it for an empty upload because empty content remains the reported result. The adapter's caller - the upload service - will catch it for a duplicate upload because a duplicate remains the domain concern, not the adapter's one. These are different decisions made by different owners.&lt;/p&gt;

&lt;h2&gt;
  
  
  One String, Two Names
&lt;/h2&gt;

&lt;p&gt;Naming was not cosmetic in this refactoring. The application has a document UUID. The S3 adapter uses an object key. They were used in the multiple adapter logging methods extracted from the main &lt;code&gt;store()&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;logFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentUUID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;documentStorageKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MinimalRAGException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The document UUID is part of the key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storedObjectKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`raw/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;documentUUID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The domain value object returns it as &lt;code&gt;document_storage_key&lt;/code&gt;, because it is document metadata from the domain side. Inside the S3 adapter, it is &lt;code&gt;storedObjectKey&lt;/code&gt;, because it is passed to the AWS SDK as &lt;code&gt;Key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The difference matters most when deleting an object.&lt;/p&gt;

&lt;p&gt;At one point, removal was proposed as &lt;code&gt;remove(documentUUID)&lt;/code&gt;, with the adapter reconstructing &lt;code&gt;raw/&amp;lt;UUID&amp;gt;&lt;/code&gt;. It looked tidy. But the exact key is already known after &lt;code&gt;store()&lt;/code&gt; returns. Rebuilding it makes removal depend on a key layout which may later change. Passing the known stored object key is simpler and more honest.&lt;/p&gt;

&lt;p&gt;The final refactoring replaces multiple log methods with one (Parameterize Method), parameterized with an object instead of a long list of positional parameters (Introduce Parameter Object). The method signature becomes 2x shorter, with intention-revealing names that are clear at first sight.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;logStorageEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TLogStorageEventOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Logging Had Become Its Own Mini-Application
&lt;/h2&gt;

&lt;p&gt;This refactoring describes the other dimension of the refactoring result shown above.&lt;/p&gt;

&lt;p&gt;The adapter had a method for successful storage. Another for successful removal. Another for S3 failure. Another for validation failure. Another for cleanup failure.&lt;/p&gt;

&lt;p&gt;They all built nearly the same event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ELogEventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INTEGRATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;storage_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;storedObjectKey&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different logging events are useful. Five separate ways to construct 70%-identical logging event are not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;logSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentUUID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;documentStorageKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actualByteSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;httpStatusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where a long argument list becomes a real problem. Six positional arguments might compile. Nobody wants to remember their order during a production incident.&lt;/p&gt;

&lt;p&gt;The final code uses the single logging method and one parameter object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TLogStorageEventOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TLogStorageSuccessOptions&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ELogLevel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ELogOutcome&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;MinimalRAGException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logStorageEvent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ELogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WARN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;eventName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EDocumentStorageLogEventName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DOCUMENT_STORAGE_VALIDATION_FAILED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ELogOutcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;operationName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DOCUMENT_STORAGE_LOG_CONSTANTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;storedObjectKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;actualByteSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;countedStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actual_byte_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rejection&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The refactoring reduced roughly 90 lines the reader previously had to scan to about 30.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deeply Buried Intention
&lt;/h2&gt;

&lt;p&gt;The builder of the logged object initially contained code like this, which is completely opaque:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogOperationVO&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="c1"&gt;// other fields&lt;/span&gt;
        &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;request_id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provider_request_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That conditional object spread was very far from readable.&lt;/p&gt;

&lt;p&gt;I extracted values into interim constants with intention-revealing names and used them in the logged object construction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Extract all ternaries to the clearly named constants&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actualByteSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actualByteSize&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;httpStatusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;httpStatusCode&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;http_status_code&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;providerRequestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storageError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;
 &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogErrorVO&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DOCUMENT_STORAGE_LOG_CONSTANTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dependency&lt;/span&gt;
   &lt;span class="p"&gt;})&lt;/span&gt;
 &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Call the logger&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogOperationVO&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Other fields&lt;/span&gt;
  &lt;span class="na"&gt;actual_byte_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;actualByteSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;http_status_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;httpStatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;provider_request_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;providerRequestId&lt;/span&gt;
 &lt;span class="p"&gt;}),&lt;/span&gt;
 &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;storageError&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the final log event construction boring. That is good. Logging is repeated often enough throughout an application that it should better be DRY.&lt;/p&gt;

&lt;h2&gt;
  
  
  Actual Refactorings Used
&lt;/h2&gt;

&lt;p&gt;For readers who care about Fowler’s refactoring names, here is the short record.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make The Public Storage Flow Readable
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Fowler refactoring&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Move inline upload and its error handling into &lt;code&gt;uploadToStorage(...)&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Extract Method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move raw S3 error conversion into &lt;code&gt;normalizeStorageFailure(...)&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Extract Method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move empty-object deletion into &lt;code&gt;cleanupStoredObject(...)&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Extract Method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move response and empty-stream checks into named methods.&lt;/td&gt;
&lt;td&gt;Extract Method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move successful-storage logging out of &lt;code&gt;store()&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Extract Method&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Clarify Storage Vocabulary and Logging
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Fowler refactoring&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;documentUuid&lt;/code&gt; to &lt;code&gt;documentUUID&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Rename Variable / Rename Parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeated S3 literals into &lt;code&gt;DOCUMENT_STORAGE_LOG_CONSTANTS&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Replace Magic Literal with Symbolic Constant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;documentStorageKey&lt;/code&gt; to &lt;code&gt;storedObjectKey&lt;/code&gt; inside the adapter.&lt;/td&gt;
&lt;td&gt;Rename Variable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long logging parameters into &lt;code&gt;TLogStorageEventOptions&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Introduce Parameter Object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conditional payload expressions into named locals.&lt;/td&gt;
&lt;td&gt;Extract Variable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate log methods into &lt;code&gt;logStorageEvent(...)&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Extract Method&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The public &lt;code&gt;remove(storedObjectKey)&lt;/code&gt; method is not a Fowler refactoring. It is a new adapter capability. The validation event rename is not one either. It changes the meaning of the logged outcome. Calling every improvement a refactoring only makes the word less useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;The adapter did not become magically small. It should not. It now does more than it did before: it exposes removal, logs useful operational information, returns a named S3 response type, and gives the upload service a clean cleanup operation.&lt;/p&gt;

&lt;p&gt;And the public path is now readable as a path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uploadToStorage&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throwIfS3Error&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throwIfUploadedFileIsEmpty&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logStorageEvent&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the result I want from refactoring - saving the reader's cognitive capacity for the really important work: deciding what, when and how the application should do in business and integration terms.&lt;/p&gt;

&lt;p&gt;NB: The initial code implementation I had to refactor was written by AI.&lt;/p&gt;

</description>
      <category>node</category>
      <category>software</category>
      <category>backend</category>
      <category>microservices</category>
    </item>
    <item>
      <title>AI Prototype as an Executable Requirements Draft</title>
      <dc:creator>Valentine Shi</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:28:06 +0000</pubDate>
      <link>https://dev.to/valentineshi-dev/ai-prototype-as-an-executable-requirements-draft-k73</link>
      <guid>https://dev.to/valentineshi-dev/ai-prototype-as-an-executable-requirements-draft-k73</guid>
      <description>&lt;p&gt;10× Faster Development with AI prototypes.&lt;/p&gt;




&lt;p&gt;A prototype generated by a non-technical stakeholder can be very useful, but I do not treat it as an unfinished production system.&lt;/p&gt;

&lt;p&gt;I treat it as an executable requirements draft.&lt;/p&gt;

&lt;p&gt;The prototype gives stakeholders and domain experts something concrete to think of, build, analyze and react to, which makes product ideation and requirements elicitation much faster. Instead of discussing an abstract idea, they can directly build their ideas and visions into a prototype, actually designing the product without distracting business analytics or engineers.&lt;/p&gt;

&lt;p&gt;The prototype code itself is a throwaway. It is usually written in a non-modular, code-and-fix style; it may lack meaningful error handling, fail to cover the use cases fully, or contain security flaws. Trying to clean it up directly can take longer than building the product correctly from the extracted requirements. But it already contains roughly formalized live product intentions, expressed by and aligned with non-technical stakeholders.&lt;/p&gt;

&lt;p&gt;When the prototype is made, the actual engineering starts. I analyze the prototype with an AI agent and extract actors, use cases, business rules, user stories, scenarios, failure modes and edge cases. Then I model the domain context, components, interfaces and core workflows, align the result with stakeholders, and only after that design and implement the real system.&lt;/p&gt;

&lt;p&gt;The speed gain comes from shortened ideation, stakeholder alignment and requirements formalization phases. That is the AI prototype part. It also comes from using AI across the whole engineering process, not only for code generation. It helps analyze the prototype, maintain C4 and UML models, update OpenAPI contracts, explore alternatives, generate tests and code, and keep related artifacts aligned when the design changes.&lt;/p&gt;

&lt;p&gt;As one recent example, I designed a queue and transactional outbox subsystem, then aligned and refactored the surrounding architectural and behavioral models and aligned them with the contract and code in about four to five hours. Without AI assistance, that alignment work alone would take me a day or two.&lt;/p&gt;

&lt;p&gt;Based on my practical experience, three to ten times faster is a reasonable ballpark for the entire stack of tasks, depending on the system and how much uncertainty exists at the beginning. It is not a universal benchmark, but it is also not only faster typing.&lt;/p&gt;

&lt;p&gt;Engineering is freed from a big portion of requirements elicitation, prototyping and alignment and is granted more speed in analysis, modeling and implementation. I find that I enjoy development noticeably more than before AI. It is worth mentioning that I have greatly enjoyed it all the time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
