<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Packages on Roberto Selbach</title><link>https://rselbach.com/tags/packages/</link><description>Recent content in Packages on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Thu, 17 Apr 2014 12:30:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/packages/index.xml" rel="self" type="application/rss+xml"/><item><title>Small Go packages and boring APIs</title><link>https://rselbach.com/small-go-packages-and-boring-apis/</link><pubDate>Thu, 17 Apr 2014 12:30:00 +0000</pubDate><guid>https://rselbach.com/small-go-packages-and-boring-apis/</guid><description>&lt;p&gt;I split a package this week because its name had become meaningless. &lt;code&gt;util&lt;/code&gt; contained HTTP parsing, identifiers, retry logic, and one function that formatted dates. Importing it told a reader only that I had eventually run out of nouns.&lt;/p&gt;
&lt;p&gt;My first repair was worse: one package per file. That produced &lt;code&gt;retry&lt;/code&gt;, &lt;code&gt;identifier&lt;/code&gt;, &lt;code&gt;httputil&lt;/code&gt;, and &lt;code&gt;dateutil&lt;/code&gt;, each with one exported function and several awkward dependencies. Package boundaries are not decorative folders. They should group a coherent capability and hide its implementation.&lt;/p&gt;
&lt;p&gt;The useful split placed request parsing beside the server code that owns its rules. Identifier generation became a small package because several unrelated commands use the same format. Date formatting stayed with the output package that defines that presentation.&lt;/p&gt;
&lt;p&gt;I also removed exported names. This API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RetryManager&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;RetryCount&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;RetryDelay&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NewRetryManager&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;count&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;delay&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;) &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;RetryManager&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;RetryManager&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;ExecuteRetryOperation&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;f&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;became:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Retry&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;delay&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;f&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There was no durable manager state and only one operation. The type existed because objects felt respectable. The function is harder to misuse, easier to test, and gives me fewer compatibility promises.&lt;/p&gt;
&lt;p&gt;Exported identifiers are the public surface, even before a package reaches version one. Renaming one breaks every importer. Adding a method to an interface also breaks implementations outside the package, so I prefer accepting the smallest interface at the point of use rather than publishing a large &amp;ldquo;service&amp;rdquo; interface beside a concrete type.&lt;/p&gt;
&lt;p&gt;Package names deserve the same economy. Callers write the package qualifier, so &lt;code&gt;httpclient.Client&lt;/code&gt; stutters while &lt;code&gt;client.Client&lt;/code&gt; or a more specific package often reads naturally. I avoid &lt;code&gt;common&lt;/code&gt;, &lt;code&gt;base&lt;/code&gt;, and &lt;code&gt;misc&lt;/code&gt;; they attract unrelated code through gravity rather than design.&lt;/p&gt;
&lt;p&gt;The test after the reorganization was not just &lt;code&gt;go test ./...&lt;/code&gt;. I opened several call sites and read them without the implementation. If &lt;code&gt;id.New()&lt;/code&gt; and &lt;code&gt;server.ParseRequest(r)&lt;/code&gt; communicated enough, the boundary was doing work. If understanding required knowing which former utility file held the function, I had merely rearranged shelves.&lt;/p&gt;
&lt;p&gt;Errors are part of the surface too. Callers should not need to compare prose. When they must distinguish a condition, I expose a stable sentinel error or a small concrete error type and document it. Most internal errors remain application details rather than becoming a taxonomy the package must support forever.&lt;/p&gt;
&lt;p&gt;Small packages are useful. Tiny packages are not automatically virtuous. I now split around ownership and stable concepts, not line count. That gives me APIs boring enough that I can return to the actual program.&lt;/p&gt;</description></item></channel></rss>