<?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: hourin</title>
    <description>The latest articles on DEV Community by hourin (@hourinx).</description>
    <link>https://dev.to/hourinx</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%2F4214850%2Fc8ec015c-f39e-4bcb-9d98-7c4c9272fa88.png</url>
      <title>DEV Community: hourin</title>
      <link>https://dev.to/hourinx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hourinx"/>
    <language>en</language>
    <item>
      <title>How I Split an Avatar Pipeline Between v1 and v2</title>
      <dc:creator>hourin</dc:creator>
      <pubDate>Fri, 30 Oct 2026 14:14:29 +0000</pubDate>
      <link>https://dev.to/hourinx/how-i-split-an-avatar-pipeline-between-v1-and-v2-1paa</link>
      <guid>https://dev.to/hourinx/how-i-split-an-avatar-pipeline-between-v1-and-v2-1paa</guid>
      <description>&lt;h3&gt;
  
  
  The &lt;code&gt;gdenticon&lt;/code&gt; Refactoring Log — Part 2
&lt;/h3&gt;

&lt;h2&gt;
  
  
  The part that actually needed refactoring
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gdenticon&lt;/code&gt; v1 could already generate avatars as a Go package. It had mirrored grids, pattern filtering, and deterministic colour selection. Judged only by the result, it was not an unusable release.&lt;/p&gt;

&lt;p&gt;Its use was still centred on Go code and local PNG files. Callers assembled the settings themselves and passed them to &lt;code&gt;Save&lt;/code&gt; for disk output. Later, I wanted &lt;code&gt;gdenticon&lt;/code&gt; to work as a standalone tool, produce several image formats, and send the same avatar to memory, an HTTP response, or another destination. The original API was no longer a comfortable fit for those uses.&lt;/p&gt;

&lt;p&gt;I could have kept extending the old structure, but each change spread beyond one layer. &lt;code&gt;WritePNG&lt;/code&gt; did more than encode PNG: it accepted the original identifier and called &lt;code&gt;Generate&lt;/code&gt; internally. The outer &lt;code&gt;Save&lt;/code&gt; function then handled content hashing, filename selection, and persistence. Adding SVG or JPEG would either duplicate a similar entry point or make &lt;code&gt;Save&lt;/code&gt; responsible for a growing set of formats and output rules.&lt;/p&gt;

&lt;p&gt;That became the reason to start v2. Before adding more features, I needed to split the fixed path into three steps: produce a reusable avatar, choose an encoding, and let the caller choose the destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  v1: a Go package that eventually reached &lt;code&gt;Save&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;v1 had no CLI. Callers first assembled a &lt;code&gt;model.Image&lt;/code&gt; in Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GridSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ImageSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Mirror&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;gdenticon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHA256&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 value mixed two kinds of settings. Grid, mirroring, digest, and colours described how to generate the avatar. &lt;code&gt;ImageSize&lt;/code&gt; and &lt;code&gt;Format&lt;/code&gt; described how to encode it.&lt;/p&gt;

&lt;p&gt;They then called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gdenticon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Save&lt;/code&gt; called &lt;code&gt;WritePNG&lt;/code&gt;, and &lt;code&gt;WritePNG&lt;/code&gt; called &lt;code&gt;Generate&lt;/code&gt;. The complete path looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go caller
  → model.Image
  → Save
  → WritePNG
  → Generate
  → encode PNG into bytes.Buffer
  → derive a filename from the bytes
  → write the file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F41zg0s9t5mkbexlwmku7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F41zg0s9t5mkbexlwmku7.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The path worked, but it left several problems for the next round of changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem was not PNG; it was the cost of adding another encoder
&lt;/h3&gt;

&lt;p&gt;The v1 &lt;code&gt;model.Image&lt;/code&gt; reserved names for SVG, PNG, JPEG, GIF, BMP, TIFF, and WEBP. Those constants were placeholders for future output work. They did not mean every format was implemented, and their presence was not a refactoring argument.&lt;/p&gt;

&lt;p&gt;The architectural problem was the call direction. &lt;code&gt;Save&lt;/code&gt; called &lt;code&gt;WritePNG&lt;/code&gt;, and &lt;code&gt;WritePNG&lt;/code&gt; triggered &lt;code&gt;Generate&lt;/code&gt;. Continuing with the same design would either create parallel entry points such as &lt;code&gt;WriteJPEG(id, img)&lt;/code&gt; and &lt;code&gt;WriteSVG(id, img)&lt;/code&gt;, or turn &lt;code&gt;Save&lt;/code&gt; into a dispatcher that still owned generation and persistence.&lt;/p&gt;

&lt;p&gt;Both approaches could work. Neither made an encoder independently replaceable. Every new format would still touch the original input, generation settings, and file policy. That was the signal to change the boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;WritePNG&lt;/code&gt; knew about input, generation, and encoding
&lt;/h3&gt;

&lt;p&gt;The v1 &lt;code&gt;WritePNG&lt;/code&gt; accepted the original identifier and the complete &lt;code&gt;model.Image&lt;/code&gt;, not an avatar that had already been generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;WritePNG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageSize&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;PNG output became another entry into the generation pipeline. A caller that already had an &lt;code&gt;Icon&lt;/code&gt; had no common &lt;code&gt;Write&lt;/code&gt; function to continue with. Every additional encoder would also need a decision about generation and size validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The core package also owned the filesystem
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Save&lt;/code&gt; selected the temporary directory, created the &lt;code&gt;gdenticon&lt;/code&gt; folder, derived a content-based filename, and wrote the file. That was convenient for callers that wanted a local file, but it was not a natural policy for every use case.&lt;/p&gt;

&lt;p&gt;An HTTP response, an object-storage upload, or an in-memory result does not need a directory or filename. Once local persistence became the default endpoint, other callers had to bypass &lt;code&gt;Save&lt;/code&gt; and reconstruct the path themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  The full encoded image was buffered first
&lt;/h3&gt;

&lt;p&gt;To derive a filename from the output, v1 encoded the complete PNG into a &lt;code&gt;bytes.Buffer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;WritePNG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;0o644&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A default-sized avatar is small, so this was not an immediate failure. It did mean that saving waited for the full encoding, then read the same bytes for hashing and disk output. That path was worth removing before considering larger batches.&lt;/p&gt;

&lt;p&gt;I am not attaching a performance number to this structural change. Without a benchmark, less buffering is not evidence of a measured speedup. What can be stated is that the new save path no longer requires the core package to retain the complete encoded image.&lt;/p&gt;

&lt;h2&gt;
  
  
  The v2 goal: separate three different jobs
&lt;/h2&gt;

&lt;p&gt;The issue was not the number of files or how the folders looked. Three different jobs had become one fixed route:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;derive an avatar from the input;&lt;/li&gt;
&lt;li&gt;encode that avatar as an image format;&lt;/li&gt;
&lt;li&gt;decide where the encoded image belongs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;v2 gives each job a boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GenerateOptions
  → Generate
  → Icon
  → Write(io.Writer)
  → file, memory, HTTP response, or object storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern algorithm remains inside &lt;code&gt;Generate&lt;/code&gt;. PNG, SVG, and the other encoders belong to &lt;code&gt;Write&lt;/code&gt;. Directories, cache entries, and replacement policy move to the filesystem package. The CLI composes those parts without moving its persistence decisions into the core package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Split generation settings from encoding settings
&lt;/h2&gt;

&lt;p&gt;v2 removed the separate &lt;code&gt;model&lt;/code&gt; package and moved its public types into &lt;code&gt;gdenticon&lt;/code&gt;. More importantly, the old &lt;code&gt;model.Image&lt;/code&gt; became two option types.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GenerateOptions&lt;/code&gt; contains the values needed to derive a logical avatar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GenerateOptions&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GridSize&lt;/span&gt;   &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Mirror&lt;/span&gt;     &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Style&lt;/span&gt;      &lt;span class="n"&gt;PatternStyle&lt;/span&gt;
    &lt;span class="n"&gt;Digest&lt;/span&gt;     &lt;span class="n"&gt;DigestFunc&lt;/span&gt;
    &lt;span class="n"&gt;Foreground&lt;/span&gt; &lt;span class="n"&gt;ColorFunc&lt;/span&gt;
    &lt;span class="n"&gt;Background&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;
    &lt;span class="n"&gt;Format&lt;/span&gt;     &lt;span class="n"&gt;Format&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;WriteOptions&lt;/code&gt; contains values used during encoding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;WriteOptions&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Size&lt;/span&gt;        &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;JPEGQuality&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Format&lt;/code&gt; remains on &lt;code&gt;GenerateOptions&lt;/code&gt; and &lt;code&gt;Icon&lt;/code&gt; so the generated value carries the encoder selected for the next stage. Pixel-size validation is delayed until &lt;code&gt;Write&lt;/code&gt;. SVG does not need to satisfy raster-size constraints, while PNG and JPEG do.&lt;/p&gt;

&lt;p&gt;The point is not the extra struct. Generation no longer has to carry every rule imposed by an encoder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make &lt;code&gt;Icon&lt;/code&gt; the real intermediate value
&lt;/h2&gt;

&lt;p&gt;v2 &lt;code&gt;Generate&lt;/code&gt; produces an &lt;code&gt;Icon&lt;/code&gt; and stops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gdenticon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Icon&lt;/code&gt; stores the grid, filled cells, colours, and target format. It describes the avatar without being an image file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GridSize&lt;/span&gt;   &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Filled&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Background&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;
    &lt;span class="n"&gt;Foreground&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;
    &lt;span class="n"&gt;Format&lt;/span&gt;     &lt;span class="n"&gt;Format&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;v1 already had an &lt;code&gt;Icon&lt;/code&gt;; the concept itself is not new. The change is that encoders now consume this value directly instead of accepting an identifier and regenerating it.&lt;/p&gt;

&lt;p&gt;This also marks the limit of the architectural refactoring. Hash-derived entropy, mirrored grids, density limits, four-connected-component filtering, and colour selection already existed in v1. v2 largely kept that core while changing how the surrounding code reaches and leaves it.&lt;/p&gt;

&lt;p&gt;There is still room to optimise the algorithm, but changing it at the same time as the output pipeline would make the source of any improvement harder to measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use one &lt;code&gt;Write&lt;/code&gt; entry for every format
&lt;/h2&gt;

&lt;p&gt;The encoding API is now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;icon&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="n"&gt;WriteOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Write&lt;/code&gt; selects PNG, SVG, JPEG, GIF, BMP, or TIFF from &lt;code&gt;icon.Format&lt;/code&gt;. Callers no longer need one public function per encoder or the original identifier.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;io.Writer&lt;/code&gt; boundary is the more important part. The core package writes encoded bytes without creating or closing a file. The destination can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;bytes.Buffer&lt;/code&gt; in memory;&lt;/li&gt;
&lt;li&gt;an &lt;code&gt;http.ResponseWriter&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;an &lt;code&gt;os.File&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;an object-storage upload stream;&lt;/li&gt;
&lt;li&gt;an &lt;code&gt;io.MultiWriter&lt;/code&gt; with several destinations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multi-format support is therefore part of the real output path rather than a list of constants. The CLI now exposes &lt;code&gt;--format&lt;/code&gt;, and that choice reaches the shared &lt;code&gt;Write&lt;/code&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move persistence out of the core package
&lt;/h2&gt;

&lt;p&gt;v2 removed &lt;code&gt;Save&lt;/code&gt; from the core package and introduced &lt;code&gt;pkg/gdenticon/file&lt;/code&gt; for directories, temporary files, replacement, and content-addressed caching.&lt;/p&gt;

&lt;p&gt;The CLI composes encoding and caching like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;iconfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PutCacheEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Extension&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="m"&gt;0o644&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gdenticon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gdenticon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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 filesystem layer supplies a writer. &lt;code&gt;PutCacheEntry&lt;/code&gt; uses &lt;code&gt;io.MultiWriter&lt;/code&gt; to stream the encoding into a temporary file and SHA-256 at the same time, without first retaining the complete image in memory.&lt;/p&gt;

&lt;p&gt;Cache hits, concurrent creation, and Windows replacement behaviour belong here. They matter to the CLI but are not identicon-generation rules. A caller that only needs bytes in memory can ignore this package completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduce the CLI in v2.0.0
&lt;/h2&gt;

&lt;p&gt;v1 was primarily a Go package. v2.0.0 introduced the CLI, which had to translate command-line strings into &lt;code&gt;GenerateOptions&lt;/code&gt; and &lt;code&gt;WriteOptions&lt;/code&gt; before joining the new generation, encoding, and filesystem pipeline.&lt;/p&gt;

&lt;p&gt;v2.0.0 supports both direct generation and an interactive mode, so &lt;code&gt;main&lt;/code&gt; chooses how input is collected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;runProgram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stderr&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;interactiveRequested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;runInteractive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stderr&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;Interactive mode does not duplicate option parsing or generation. It passes the arguments following &lt;code&gt;generate&lt;/code&gt; through the same Cobra command tree and records the result through a callback. Direct and interactive requests share the same path after input collection.&lt;/p&gt;

&lt;p&gt;Usage errors still return &lt;code&gt;2&lt;/code&gt; with command help, while runtime failures return &lt;code&gt;1&lt;/code&gt; with their cause. Interactive mode keeps the session alive and changes only how the same errors are presented.&lt;/p&gt;

&lt;p&gt;This work is far from the avatar algorithm, but it keeps the newly added terminal state and session management out of the package that generates images.&lt;/p&gt;

&lt;h2&gt;
  
  
  The current v2 request path
&lt;/h2&gt;

&lt;p&gt;Running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;gdenticon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alice&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;example.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now follows this route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main
  → choose direct or interactive input
  → validate and translate CLI options
  → Generate an Icon
  → Write the selected format to an io.Writer
  → store or reuse a content-addressed file
  → print the final path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared with v1, the pattern algorithm has not been wrapped in additional layers. The responsibilities around it are simply clearer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Generate&lt;/code&gt; does not own image size or a file path;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Write&lt;/code&gt; does not regenerate the avatar or select a destination;&lt;/li&gt;
&lt;li&gt;the filesystem package does not know about digests or grids;&lt;/li&gt;
&lt;li&gt;the CLI only connects user input to those capabilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the main v1-to-v2 refactoring. It does not prove a speedup by itself, but it creates places where later changes can be measured and replaced independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What remains open
&lt;/h2&gt;

&lt;p&gt;Separating generation, encoding, and persistence leaves plenty of work inside avatar generation itself.&lt;/p&gt;

&lt;p&gt;The current algorithm turns a digest into a renewable bitstream, generates a mirrored grid, and rejects candidates that are too empty, too full, or too fragmented using density and four-connected components. That core idea already existed in v1; v2 did not replace it wholesale.&lt;/p&gt;

&lt;p&gt;The next questions are more specific: which calculations can be removed, whether candidate scoring should change, where the grid and raster paths allocate unnecessarily, and what each change does in a benchmark. Until those measurements exist, I will not present an architectural cleanup as a performance result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;The next part will move inside &lt;code&gt;Generate&lt;/code&gt;, starting with how digest bits reach the mirrored grid and how &lt;code&gt;balanced&lt;/code&gt; filters candidates, then identifying the parts that still have room for optimisation.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/hourinX/gdenticon" rel="noopener noreferrer"&gt;https://github.com/hourinX/gdenticon&lt;/a&gt;&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>architecture</category>
      <category>go</category>
      <category>api</category>
    </item>
    <item>
      <title>Why Am I Refactoring My Own Open-Source Project?</title>
      <dc:creator>hourin</dc:creator>
      <pubDate>Thu, 29 Oct 2026 15:23:16 +0000</pubDate>
      <link>https://dev.to/hourinx/why-am-i-refactoring-my-own-open-source-project-881</link>
      <guid>https://dev.to/hourinx/why-am-i-refactoring-my-own-open-source-project-881</guid>
      <description>&lt;h3&gt;
  
  
  The &lt;code&gt;gdenticon&lt;/code&gt; Refactoring Log — Part 1
&lt;/h3&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;gdenticon&lt;/code&gt; is
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gdenticon&lt;/code&gt; is an identicon generator I wrote in Go. Give it a username, email address, or another string, and it produces a stable pixel avatar. The same input gives the same result, which makes it useful as a default image when a user has not uploaded one.&lt;/p&gt;

&lt;p&gt;The project already works as both a command-line tool and a Go package. It can output PNG, SVG, JPEG, GIF, BMP, and TIFF.&lt;/p&gt;

&lt;p&gt;This series, however, is not an introduction to a finished project. I am going back through it and refactoring parts of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7tfjeddfk0opcjxdfii3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7tfjeddfk0opcjxdfii3.png" alt=" " width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;gdenticon&lt;/code&gt; exists
&lt;/h2&gt;

&lt;p&gt;I started the project because the Go identicon libraries I could find never quite matched what I needed.&lt;/p&gt;

&lt;p&gt;Some produced patterns that felt too dense. Some were effectively limited to PNG. Others required more setup code than I wanted for generating one small avatar.&lt;/p&gt;

&lt;p&gt;That did not make those projects bad. They were simply designed around needs that were different from mine. I wanted a smaller tool with calmer-looking patterns, flexible output formats, a convenient CLI, and an API that could be dropped into another Go project without much preparation.&lt;/p&gt;

&lt;p&gt;After failing to find exactly that, I decided to build it. That was the beginning of &lt;code&gt;gdenticon&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjc982ez9yf6vgc0wbh4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjc982ez9yf6vgc0wbh4n.png" alt=" " width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why refactor something that already works
&lt;/h2&gt;

&lt;p&gt;Avatar generation is already implemented, but working features do not automatically mean the current solution is good enough.&lt;/p&gt;

&lt;p&gt;As the project grew, I began to notice problems that did not appear as immediate failures. Some responsibilities were not separated clearly enough, so changing one area meant thinking about several others. Pattern generation still had room to become more efficient. Supporting more output formats and ways to use the package also made the original organisation less comfortable.&lt;/p&gt;

&lt;p&gt;None of this stops the program from producing an avatar today. It does affect how easily the project can change tomorrow.&lt;/p&gt;

&lt;p&gt;I would rather address that while the codebase is still small than wait until every new feature has to work around an old decision. The refactoring is therefore not a rewrite from scratch, and it is not an excuse to introduce a more elaborate architecture.&lt;/p&gt;

&lt;p&gt;The working behaviour will remain. The goal is to make responsibilities clearer, remove unnecessary computation and memory use, keep the API simple, and make later changes to formats and pattern generation less disruptive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want &lt;code&gt;gdenticon&lt;/code&gt; to become
&lt;/h2&gt;

&lt;p&gt;My expectation for &lt;code&gt;gdenticon&lt;/code&gt; has not changed. It should remain a small, practical tool rather than a collection of abstractions built to demonstrate techniques.&lt;/p&gt;

&lt;p&gt;Generating an avatar should take very little code. The tool should feel natural in a terminal and inside a web service, background job, or another Go project. Results should be deterministic, patterns should not feel overcrowded, and the output should not be tied to one image format.&lt;/p&gt;

&lt;p&gt;The inside of the project should be approachable as well. Someone opening the repository for the first time should be able to follow how input becomes an avatar. Adding a format or adjusting the pattern should not require understanding every package. When something fails, its owner should be reasonably easy to locate.&lt;/p&gt;

&lt;p&gt;Performance is part of this work, but I do not want to label the project as fast before measuring it. Each optimisation should come with a reproducible test. If an idea makes little difference, that result is worth recording too. Clever-looking code is not evidence of an improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recording the process this time
&lt;/h2&gt;

&lt;p&gt;When working on a small personal project, it is easy to focus on getting the feature finished. The reasoning behind a design, the alternatives that were tried, and the decisions that were later reversed often feel unforgettable at the time. A few months later, much of that context is gone.&lt;/p&gt;

&lt;p&gt;This time I want to keep the process alongside the code. The series will cover not only the final implementation, but also the problem behind each change, the alternatives I did not choose, and whether the measurements matched my expectations.&lt;/p&gt;

&lt;p&gt;Later parts will move through the program entry and error handling, the split between generation and output, the avatar pattern, benchmarks, and other issues that appear during the refactoring. Each part will stay with one main problem instead of trying to explain the entire project at once.&lt;/p&gt;

&lt;p&gt;There will be blind spots in a one-person project. If you find a weakness in the code or see a better direction, I would be glad to hear it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/hourinX/gdenticon" rel="noopener noreferrer"&gt;https://github.com/hourinX/gdenticon&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;The next part will start with the overall structure and follow one avatar request from the program entry to the final encoded image.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>refactoring</category>
      <category>go</category>
    </item>
  </channel>
</rss>
