<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Torsten Curdt writing about tech</title>
  <link href="https://torstencurdt.com/tech/feed.xml" rel="self"/>
  <updated>2020-01-02T00:00:00Z</updated>
  <id>https://torstencurdt.com/tech/feed.xml</id>
  <author>
    <name>Torsten Curdt</name>
    <email>hi@torstencurdt.com</email>
  </author>
  
  <entry>
    <id>https://torstencurdt.com/tech/qr-barcode-automation/</id>
    <title>Scanning QR Codes on Mac for Automation</title>
    <link href="https://torstencurdt.com/tech/qr-barcode-automation/"/>
    <updated>2020-01-02T00:00:00Z</updated>
    <content type="html">&lt;p&gt;It started with an invoice. I wanted to scan the QR code on a German Überweisung (payment transfers) and have it open directly in my banking software. No typing, no copy-paste errors on those long IBANs. Seemed like it should be easy.&lt;/p&gt;
&lt;p&gt;It wasn’t. On iOS, the camera app handles QR codes natively. But not payment transfers either. On the Mac? Nothing. There was no straightforward way to scan a barcode from the built-in camera and do something useful with the result.&lt;/p&gt;
&lt;p&gt;Then I ran into the same wall again with two-factor authentication. A service showed me a QR code containing a TOTP secret key and I was supposed to scan it with my phone. But I was sitting right in front of my Mac, staring at the code on my screen. Why couldn’t I just scan it right there?&lt;/p&gt;
&lt;p&gt;That frustration is what led me to build &lt;a href=&quot;https://apps.apple.com/de/app/scancode/id1482517543?mt=12&quot;&gt;ScanCode&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-scancode-does&quot; tabindex=&quot;-1&quot;&gt;What ScanCode does&lt;/h2&gt;
&lt;p&gt;ScanCode scans QR codes and barcodes on macOS. It reads from the built-in camera or directly from the screen. Once it decodes the content, it can act on it immediately or pass it along to scripts for automation.&lt;/p&gt;
&lt;p&gt;The first version scanned codes and handed the raw data to external actions via Automator workflows or shell scripts. Everything else was up to you.&lt;/p&gt;
&lt;p&gt;Version 2 is different. Most common actions are now built in. ScanCode recognizes the type of content and offers the right action automatically. You can still pipe data into custom scripts, but for everyday use cases you no longer need to.&lt;/p&gt;
&lt;figure data-type=&quot;image&quot;&gt;&lt;img src=&quot;https://torstencurdt.com/tech/qr-barcode-automation/scancode_wide.jpg&quot; alt=&quot;ScanCode triggering a Bash Script&quot; /&gt;&lt;figcaption&gt;ScanCode triggering a Bash Script&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Here is what it handles out of the box:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Type&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;What’s encoded&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;What ScanCode does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;URL&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;http://&lt;/code&gt; or &lt;code&gt;https://&lt;/code&gt; links&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Opens in your default browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;SEPA&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Payment data (EPC, Bezahlcode, payto)&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Opens a pre-filled transfer in your banking app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;WiFi&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Network name, encryption, password&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Copies the password to your clipboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;OTP&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;otpauth://&lt;/code&gt; with secret key and parameters&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Copies/opens the key in your authenticator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;Email&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;mailto:&lt;/code&gt; with address, subject, body&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Opens your email client with message ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;Contacts&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/VCard&quot;&gt;vCard&lt;/a&gt; or MeCard data&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Adds to your address book&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;Events&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Calendar dates, times, locations&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Creates a calendar entry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;Geo&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;geo:&lt;/code&gt; URI with coordinates&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Opens in Maps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;Bitcoin&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;bitcoin:&lt;/code&gt; URI with wallet address&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Copies address or opens wallet app&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For anything that doesn’t match a known pattern, ScanCode can copy the raw text to the clipboard or save it to a file. Combined with shell scripts, you can build custom workflows for whatever you need.&lt;/p&gt;
&lt;h2 id=&quot;sepa-transfers&quot; tabindex=&quot;-1&quot;&gt;SEPA transfers&lt;/h2&gt;
&lt;p&gt;This is still the main use case for me. QR codes on European invoices encode the payment information for a bank transfer. No manual entry, no transcription errors.&lt;/p&gt;
&lt;h3 id=&quot;what-s-inside-the-qr-code&quot; tabindex=&quot;-1&quot;&gt;What’s inside the QR code&lt;/h3&gt;
&lt;p&gt;There are several standards for encoding SEPA payment data in QR codes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EPC QR codes&lt;/strong&gt; are the most widespread in the EU. The &lt;a href=&quot;https://en.wikipedia.org/wiki/EPC_QR_code&quot;&gt;European Payments Council&lt;/a&gt; defined the standard and it’s used in Austria, Belgium, Finland, Germany and The Netherlands. The encoding is straightforward. Lines of text, one field per line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;BCD
002
1
SCT
RLNWATWW
Doctors Without Borders
AT973200000000518548
EUR1500.99


Emergency Donation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;BCD&lt;/code&gt; identifies it as an EPC code. If you want to generate one yourself, there are &lt;a href=&quot;https://qrcode.tec-it.com/en/SEPA&quot;&gt;online tools&lt;/a&gt; for that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bezahlcode&lt;/strong&gt; was a German standard using a &lt;code&gt;bank://&lt;/code&gt; URI scheme:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bank://singlepaymentsepa?name={NAME}&amp;amp;reason={REASON}&amp;amp;iban={IBAN}&amp;amp;amount={AMOUNT}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The official website is long gone but the format is still understood by some banking apps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Swiss QR bill&lt;/strong&gt; uses its own format, optimized for the Swiss payment system. If you handle Swiss invoices, you’ll encounter this variant instead of EPC.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The payto URI&lt;/strong&gt; defined in &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8905.html&quot;&gt;RFC 8905&lt;/a&gt; is the standards-track approach:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;payto://iban/{IBAN}?amount=EUR:{AMOUNT}&amp;amp;message={MESSAGE}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adoption is still limited, but it’s the one backed by an RFC.&lt;/p&gt;
&lt;h3 id=&quot;getting-it-into-your-banking-app&quot; tabindex=&quot;-1&quot;&gt;Getting it into your banking app&lt;/h3&gt;
&lt;p&gt;ScanCode recognizes all these formats. For &lt;a href=&quot;https://moneymoney.app/&quot;&gt;MoneyMoney&lt;/a&gt; (my banking software of choice on Mac), it converts the scanned data into a URL scheme that MoneyMoney understands and opens a pre-filled SEPA transfer. No intermediate steps.&lt;/p&gt;
&lt;p&gt;MoneyMoney supports both the &lt;code&gt;bank://&lt;/code&gt; and &lt;code&gt;payto://&lt;/code&gt; URL schemes. ScanCode handles the conversion from EPC or whatever format the QR code uses.&lt;/p&gt;
&lt;p&gt;Other banking apps with URL scheme support work the same way. The point is that you go from paper invoice to pre-filled transfer form in a single scan.&lt;/p&gt;
&lt;h2 id=&quot;a-word-on-security&quot; tabindex=&quot;-1&quot;&gt;A word on security&lt;/h2&gt;
&lt;p&gt;Be careful with QR codes from untrusted sources. A QR code is just a way to encode text and that text could be a malicious URL or a crafted payload. The same caution you’d apply to clicking unknown links applies to scanning unknown codes.&lt;/p&gt;
&lt;p&gt;ScanCode allows you to show the decoded content before taking action, so you always get a chance to inspect what you’re about to open.&lt;/p&gt;
&lt;h2 id=&quot;from-camera-to-workflow&quot; tabindex=&quot;-1&quot;&gt;From camera to workflow&lt;/h2&gt;
&lt;p&gt;The gap between phones and Macs in terms of QR code handling has always been strange. Phones scan codes effortlessly. On the Mac - not so much. ScanCode fills that gap. Whether it’s a payment on an invoice, a WiFi password at a hotel, or a TOTP key for your authenticator, the flow is the same. Point, scan, done.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://apps.apple.com/us/app/scancode-qrcode-and-barcode/id1117496050&quot;&gt;&lt;img src=&quot;https://torstencurdt.com/tech/qr-barcode-automation/mas.svg&quot; alt=&quot;ScanCode on the Mac App Store&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <id>https://torstencurdt.com/tech/modulo-of-negative-numbers/</id>
    <title>Modulo of Negative Numbers</title>
    <link href="https://torstencurdt.com/tech/modulo-of-negative-numbers/"/>
    <updated>2011-06-28T00:00:00Z</updated>
    <content type="html">&lt;p&gt;The modulo (often just called “mod”) gives the remainder of a division. In 1801 Gauss published a book covering modular arithmetics. Later a widely accepted mathematical definition was given by &lt;a href=&quot;https://en.wikipedia.org/wiki/Donald_Knuth&quot;&gt;Donald Knuth&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mod(a, n) = a - n * floor(a / n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An integer division followed by multiplication finds the largest multiple of &lt;code&gt;n&lt;/code&gt; that fits into &lt;code&gt;a&lt;/code&gt;. Subtracting that from &lt;code&gt;a&lt;/code&gt; yields the remainder - the modulo.&lt;/p&gt;
&lt;p&gt;That sounds straightforward enough. But there’s a subtlety hiding in that &lt;code&gt;floor&lt;/code&gt; that trips up programmers across languages.&lt;/p&gt;
&lt;h2 id=&quot;remainder-vs-modulo&quot; tabindex=&quot;-1&quot;&gt;Remainder vs modulo&lt;/h2&gt;
&lt;p&gt;Most programming languages use the &lt;code&gt;%&lt;/code&gt; operator and call it “modulo”, but what they actually implement is the &lt;strong&gt;remainder&lt;/strong&gt; of a truncated division. Truncated division rounds toward zero, while the mathematical modulo uses floored division (rounding toward negative infinity). For positive numbers the results are identical. For negative numbers, they diverge.&lt;/p&gt;
&lt;p&gt;Consider &lt;code&gt;-13 mod 3&lt;/code&gt;. The floored division gives &lt;code&gt;floor(-13/3) = -5&lt;/code&gt;, so the modulo is &lt;code&gt;-13 - 3 * (-5) = 2&lt;/code&gt;. A positive result. The truncated division gives &lt;code&gt;trunc(-13/3) = -4&lt;/code&gt;, so the remainder is &lt;code&gt;-13 - 3 * (-4) = -1&lt;/code&gt;. A negative result.&lt;/p&gt;
&lt;p&gt;This is the root of a whole class of bugs.&lt;/p&gt;
&lt;h2 id=&quot;restricting-bounds&quot; tabindex=&quot;-1&quot;&gt;Restricting bounds&lt;/h2&gt;
&lt;p&gt;In programming, the modulo operator is commonly used to wrap an index around the bounds of an array or ring buffer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;values = [ 3, 4, 5 ]
index = 5
value_at_index = values[ index % values.length ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the above example &lt;code&gt;5 mod 3 = 2&lt;/code&gt; following the definition is &lt;code&gt;5 - floor(5/3)*3 = 2&lt;/code&gt;. No matter the value of &lt;code&gt;index&lt;/code&gt;, the array bounds are met.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But is that really the case?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What happens if the dividend is negative? Turns out the result of the modulo operation on negative numbers depends on the language you are using.&lt;/p&gt;
&lt;p&gt;While the code looks pretty much the same across languages, the results fall into mostly two camps.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Language&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;13 mod 3&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;-13 mod 3&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;13 mod -3&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;-13 mod -3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;C#&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;C++&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Elixir&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Erlang&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Go&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Java&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Javascript&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Kotlin&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Nim&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;OCaml&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;PHP&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Rust&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Scala&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Swift&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;V&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Crystal&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Haskell&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Lua&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Python&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Ruby&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Dart&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first group (C, Java, JavaScript, etc.) implements truncated division and the remainder takes the sign of the dividend. The second group (Python, Ruby, etc.) implements floored division and the result takes the sign of the divisor, matching the more mathematical definition.&lt;/p&gt;
&lt;p&gt;Dart is a bit of an outlier. Its &lt;code&gt;%&lt;/code&gt; operator always returns a non-negative result, regardless of the sign of either operand.&lt;/p&gt;
&lt;h2 id=&quot;zig-s-principled-approach&quot; tabindex=&quot;-1&quot;&gt;Zig’s principled approach&lt;/h2&gt;
&lt;p&gt;Zig deserves a special mention for being the most explicit about the distinction. Instead of picking one behavior and hoping developers remember which one it is, Zig provides two separate builtins: &lt;a href=&quot;https://ziglang.org/documentation/master/#@rem&quot;&gt;&lt;code&gt;@rem&lt;/code&gt;&lt;/a&gt; for the truncated remainder and &lt;a href=&quot;https://ziglang.org/documentation/master/#@mod&quot;&gt;&lt;code&gt;@mod&lt;/code&gt;&lt;/a&gt; for the floored modulo. If you pass a negative divisor, both operations produce a compile-time error. No ambiguity, no surprises.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Zig&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;13 mod 3&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;-13 mod 3&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;13 mod -3&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;-13 mod -3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;@rem&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;error&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;@mod&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;error&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is arguably the most clear design there is.&lt;/p&gt;
&lt;h2 id=&quot;language-level-fixes&quot; tabindex=&quot;-1&quot;&gt;Language-level fixes&lt;/h2&gt;
&lt;p&gt;Some languages provide built-in alternatives to the default &lt;code&gt;%&lt;/code&gt; behavior. Java added &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floorMod-int-int-&quot;&gt;&lt;code&gt;Math.floorMod()&lt;/code&gt;&lt;/a&gt; in Java 8, which gives the mathematically correct floored modulo. So if you’re a Java developer bitten by this before - there’s a one-liner fix now.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Math.floorMod(-13, 3) // returns 2, not -1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python and Ruby don’t need this since their &lt;code&gt;%&lt;/code&gt; already does floored division by default.&lt;/p&gt;
&lt;p&gt;For languages without a built-in, a simple and efficient workaround is to check the sign.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int mod(a, b) {
  c = a % b
  return (c &amp;lt; 0) ? c + b : c
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, you can apply the modulo twice.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int mod(a, b) {
  return ((a % b) + b) % b
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-even-odd-trap&quot; tabindex=&quot;-1&quot;&gt;The even/odd trap&lt;/h2&gt;
&lt;p&gt;Another related pitfall is testing whether a number is odd using the modulo operator. Given that &lt;code&gt;-13 % 2&lt;/code&gt; returns &lt;code&gt;-1&lt;/code&gt; in many languages, you should always compare against &lt;code&gt;0&lt;/code&gt; rather than &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bool is_odd(int n) {
    return n % 2 != 0; // correct: works for both 1 and -1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If performance matters and you’re dealing with powers of two, the modulo can be replaced with a bitwise operation since &lt;code&gt;x % 2^n == x &amp;amp; (2^n - 1)&lt;/code&gt; for positive divisors. So a faster &lt;code&gt;is_odd&lt;/code&gt; becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bool is_odd(int n) {
    return (n &amp;amp; 1) != 0; // note the parentheses to ensure precedence
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;in-summary&quot; tabindex=&quot;-1&quot;&gt;In summary&lt;/h2&gt;
&lt;p&gt;The modulo operator is one of those things that seems simple until it isn’t. The majority of languages implement truncated remainder rather than the mathematical modulo. The difference only shows up with negative numbers, exactly the path that is often not tested for.&lt;/p&gt;
&lt;p&gt;Know which camp your language falls into, use built-in alternatives like &lt;code&gt;Math.floorMod()&lt;/code&gt; where available, and when in doubt - write a small wrapper. For a deeper dive, see the &lt;a href=&quot;https://en.wikipedia.org/wiki/Modulo_operation&quot;&gt;Wikipedia article on the modulo operation&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <id>https://torstencurdt.com/tech/efficient-time-series-data/</id>
    <title>Time Series Rollups and Aggregation at Scale</title>
    <link href="https://torstencurdt.com/tech/efficient-time-series-data/"/>
    <updated>2011-05-15T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Many sites provide statistics for their users’ content. Play counts, page views, API calls. Usually presented as a graph over time. This is easy when aggregating over the raw events data. But that’s neither particularly fast nor scalable. As soon as the number of events or the time window gets too big this approach is no longer feasible.&lt;/p&gt;
&lt;p&gt;Today there are purpose-built time series databases that handle much of the heavy lifting. But underneath, they all rely on the same fundamental patterns. Understanding those patterns helps you pick the right tool, configure it properly, and know when to build something custom.&lt;/p&gt;
&lt;h2 id=&quot;the-landscape&quot; tabindex=&quot;-1&quot;&gt;The landscape&lt;/h2&gt;
&lt;p&gt;The number of time series databases has exploded over the past decade. Here’s a rough map.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tigerdata.com/&quot;&gt;TimescaleDB&lt;/a&gt; extends PostgreSQL with &lt;a href=&quot;https://www.tigerdata.com/docs/learn/continuous-aggregates&quot;&gt;continuous aggregates&lt;/a&gt; that automatically maintain pre-computed rollups. As new data arrives, only affected time buckets are recalculated. If you’re already running Postgres, it’s the easiest on-ramp.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://clickhouse.com/&quot;&gt;ClickHouse&lt;/a&gt; takes a different angle. Its &lt;a href=&quot;https://clickhouse.com/docs/knowledgebase/materialized-view-rollup-timeseries&quot;&gt;materialized views&lt;/a&gt; trigger on insert, shifting aggregation work from query time to write time. You define a raw table, a rollup table, and a view that connects them. Fast column-oriented storage makes it popular for analytics workloads.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.influxdata.com/&quot;&gt;InfluxDB&lt;/a&gt; was built specifically for time series from the start. Version 3.x includes a &lt;a href=&quot;https://docs.influxdata.com/influxdb3/enterprise/plugins/library/official/downsampler/&quot;&gt;downsampler plugin&lt;/a&gt; that aggregates measurements over configurable time intervals using functions like avg, sum, min, max. It also supports external stream processing through Kafka-based pipelines.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://victoriametrics.com/&quot;&gt;VictoriaMetrics&lt;/a&gt; positions itself as a faster, more scalable drop-in replacement for &lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt;. While Prometheus itself has no built-in downsampling or long-term storage, extensions like &lt;a href=&quot;https://thanos.io/&quot;&gt;Thanos&lt;/a&gt; add exactly that: automatic downsampling (5-minute resolution after 40 hours, 1-hour after 10 days) plus object storage for retention beyond what a single Prometheus instance can hold.&lt;/p&gt;
&lt;p&gt;For large-scale batch processing of time series, &lt;a href=&quot;https://spark.apache.org/&quot;&gt;Apache Spark&lt;/a&gt; and libraries like &lt;a href=&quot;https://github.com/twosigma/flint&quot;&gt;Flint&lt;/a&gt; offer distributed computation across clusters. This is the territory where you’re processing terabytes of historical data rather than serving real-time dashboards.&lt;/p&gt;
&lt;p&gt;On the managed side, &lt;a href=&quot;https://aws.amazon.com/timestream/&quot;&gt;AWS Timestream&lt;/a&gt; offers a serverless time series database that automatically moves data between in-memory and cold storage tiers based on age.&lt;/p&gt;
&lt;p&gt;Different tools, same underlying ideas.&lt;/p&gt;
&lt;h2 id=&quot;the-fundamentals&quot; tabindex=&quot;-1&quot;&gt;The fundamentals&lt;/h2&gt;
&lt;p&gt;Every one of these systems implements some variation of three core concepts: pre-aggregation, rollup strategies, and resolution tradeoffs.&lt;/p&gt;
&lt;h3 id=&quot;pre-aggregation&quot; tabindex=&quot;-1&quot;&gt;Pre-aggregation&lt;/h3&gt;
&lt;p&gt;Querying raw events is O(n) where n is the number of events. For a billion events, that’s a billion rows to scan. Pre-aggregation collapses events into time buckets ahead of time. Instead of counting a million page views for March 2024, you store a single counter: &lt;code&gt;2024-03 = 1000000&lt;/code&gt;. Query time becomes nearly constant regardless of how many events actually occurred.&lt;/p&gt;
&lt;h3 id=&quot;rollup-strategies&quot; tabindex=&quot;-1&quot;&gt;Rollup strategies&lt;/h3&gt;
&lt;p&gt;A natural choice is to roll up data into multiple time bucket granularities. By creating counters per different time units, the number of operations can be kept at a minimum while still providing fast aggregation over large time windows.&lt;/p&gt;
&lt;p&gt;Calculating an aggregate then becomes as simple as adding a few counters. It’s the sum of the most covering aggregates that fit into the given time interval.&lt;/p&gt;
&lt;p&gt;Getting the total count for the interval from 2008-11-30 until 2011-02-03 means getting 8 values and adding them.&lt;/p&gt;
&lt;figure data-type=&quot;image&quot;&gt;&lt;img src=&quot;https://torstencurdt.com/tech/efficient-time-series-data/timeseries.svg&quot; alt=&quot;The 8 rollup values used by the query&quot; /&gt;&lt;figcaption&gt;The 8 rollup values used by the query&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Choosing the time units defines the read pattern and the number of counters needed. A natural combination would be &lt;em&gt;year+month+day&lt;/em&gt; but &lt;em&gt;year+week+day&lt;/em&gt; could work too. Even just rolling up into &lt;em&gt;day&lt;/em&gt; buckets might be enough. For an informed decision, check against real user queries and calculate the required storage.&lt;/p&gt;
&lt;h3 id=&quot;resolution-tradeoffs&quot; tabindex=&quot;-1&quot;&gt;Resolution tradeoffs&lt;/h3&gt;
&lt;p&gt;More granularity means more storage. Fewer rollup levels mean faster writes but slower reads for large time ranges. Most systems let you define retention policies: keep full-resolution data for 30 days, hourly rollups for a year, daily rollups forever. This is exactly what Thanos does for Prometheus, and what TimescaleDB’s continuous aggregate policies automate.&lt;/p&gt;
&lt;p&gt;The right balance depends entirely on the use case. Real-time dashboards need second-level granularity for the last hour but nobody needs per-second data from three years ago.&lt;/p&gt;
&lt;p&gt;The fundamentals cover the read path, but the events still need to be rolled up. How that happens has evolved significantly.&lt;/p&gt;
&lt;h2 id=&quot;lambda-architecture&quot; tabindex=&quot;-1&quot;&gt;Lambda architecture&lt;/h2&gt;
&lt;p&gt;The simplest approach is a batch job that periodically recomputes all rollups from raw events. But pure batch creates lag. The &lt;a href=&quot;https://www.oreilly.com/radar/questioning-the-lambda-architecture/&quot;&gt;Lambda architecture&lt;/a&gt; closes this gap by running two parallel systems. A batch layer provides rollups up until a certain point in time. Everything past that is served from a fast in-memory layer.&lt;/p&gt;
&lt;figure data-type=&quot;image&quot;&gt;&lt;img src=&quot;https://torstencurdt.com/tech/efficient-time-series-data/realtime.svg&quot; alt=&quot;Combining Rollups and Real Time Counters&quot; /&gt;&lt;figcaption&gt;Combining Rollups and Real Time Counters&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The in-memory layer can even be transient since the eventual source of truth is always the batch layer. SoundCloud used &lt;a href=&quot;https://developers.soundcloud.com/blog/keeping-counts-in-sync/&quot;&gt;a similar architecture&lt;/a&gt; for their play count statistics, backed by Cassandra for storage and Memcached for caching. Their system called “Stitch” would regenerate aggregated data buckets through daily and monthly batch jobs while a speed layer handled real-time updates using distributed counters.&lt;/p&gt;
&lt;p&gt;The downside of Lambda is maintaining two separate code paths for the same business logic. The batch pipeline and the streaming pipeline need to produce consistent results, and keeping them in sync is tricky to get right.&lt;/p&gt;
&lt;h3 id=&quot;the-batch-layer-in-practice&quot; tabindex=&quot;-1&quot;&gt;The batch layer in practice&lt;/h3&gt;
&lt;p&gt;A naive approach is to increment counters as events arrive. A single event fans out into multiple writes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;event at 2011-03-14 =&amp;gt;
    2011 += 1
    2011-03 += 1
    2011-03-14 += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This puts a lot of write pressure on the store. An increment is not just a write but also a read. And all increments should ideally happen in a single transaction.&lt;/p&gt;
&lt;p&gt;Another option is to generate the counters from log files using a MapReduce job. The mapper emits data for per-track and global counters:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def mapper(lines):
    result = []
    for date, track in lines:
        result.append((track, date))   # per-track counter
        result.append((&amp;quot;*&amp;quot;, date))     # global counter
    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After mapping, the data gets sorted. The reducer detects changes in the incoming data, does the rollups and emits the final counts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def reducer(sorted_lines):
    prev = {&amp;quot;d&amp;quot;: &amp;quot;&amp;quot;, &amp;quot;m&amp;quot;: &amp;quot;&amp;quot;, &amp;quot;y&amp;quot;: &amp;quot;&amp;quot;, &amp;quot;t&amp;quot;: &amp;quot;&amp;quot;}
    count = {&amp;quot;d&amp;quot;: 0, &amp;quot;m&amp;quot;: 0, &amp;quot;y&amp;quot;: 0}

    for track, date in sorted_lines:
        d, m, y = date[:10], date[:7], date[:4]

        if prev[&amp;quot;t&amp;quot;]:
            if d != prev[&amp;quot;d&amp;quot;]:
                emit(prev[&amp;quot;t&amp;quot;], prev[&amp;quot;d&amp;quot;], count[&amp;quot;d&amp;quot;])
                count[&amp;quot;d&amp;quot;] = 0
            if m != prev[&amp;quot;m&amp;quot;]:
                emit(prev[&amp;quot;t&amp;quot;], prev[&amp;quot;m&amp;quot;], count[&amp;quot;m&amp;quot;])
                count[&amp;quot;m&amp;quot;] = 0
            if y != prev[&amp;quot;y&amp;quot;]:
                emit(prev[&amp;quot;t&amp;quot;], prev[&amp;quot;y&amp;quot;], count[&amp;quot;y&amp;quot;])
                count[&amp;quot;y&amp;quot;] = 0
            if track != prev[&amp;quot;t&amp;quot;]:
                count = {&amp;quot;d&amp;quot;: 0, &amp;quot;m&amp;quot;: 0, &amp;quot;y&amp;quot;: 0}

        count[&amp;quot;d&amp;quot;] += 1
        count[&amp;quot;m&amp;quot;] += 1
        count[&amp;quot;y&amp;quot;] += 1
        prev = {&amp;quot;d&amp;quot;: d, &amp;quot;m&amp;quot;: m, &amp;quot;y&amp;quot;: y, &amp;quot;t&amp;quot;: track}

    if prev[&amp;quot;t&amp;quot;]:
        emit(prev[&amp;quot;t&amp;quot;], prev[&amp;quot;d&amp;quot;], count[&amp;quot;d&amp;quot;])
        emit(prev[&amp;quot;t&amp;quot;], prev[&amp;quot;m&amp;quot;], count[&amp;quot;m&amp;quot;])
        emit(prev[&amp;quot;t&amp;quot;], prev[&amp;quot;y&amp;quot;], count[&amp;quot;y&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ideally this job creates data ready for bulk import into a fast store that can then serve web requests in a timely manner.&lt;/p&gt;
&lt;h3 id=&quot;the-partitioning-problem&quot; tabindex=&quot;-1&quot;&gt;The partitioning problem&lt;/h3&gt;
&lt;p&gt;In a distributed MapReduce setup, a partitioner splits data across machines. For this to work correctly, all events for a given track within a full year must reach the same reducer. Otherwise you can’t calculate complete yearly rollups.&lt;/p&gt;
&lt;p&gt;This is fine for per-track counters. But for global counters (the &lt;code&gt;&amp;quot;*&amp;quot;&lt;/code&gt; key), it means all events of an entire year funnel through a single machine. That’s a hard bottleneck. Adding more machines to the cluster won’t help because one reducer still has to see everything.&lt;/p&gt;
&lt;p&gt;The fix is a secondary job. The first pass computes partial rollups per partition. The second pass combines them. It adds complexity, so whether it’s worth it depends on the data volume and how fast the batch needs to complete.&lt;/p&gt;
&lt;h2 id=&quot;kappa-architecture&quot; tabindex=&quot;-1&quot;&gt;Kappa architecture&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Kappa_architecture&quot;&gt;Kappa architecture&lt;/a&gt;, proposed by Jay Kreps, simplifies things by eliminating the batch layer entirely. Instead of maintaining two pipelines, all data flows through a single stream processing system. Historical reprocessing is handled by replaying the event log from the beginning. One codebase, one set of semantics. With modern stream processing frameworks like &lt;a href=&quot;https://flink.apache.org/&quot;&gt;Apache Flink&lt;/a&gt; or &lt;a href=&quot;https://kafka.apache.org/documentation/streams/&quot;&gt;Kafka Streams&lt;/a&gt;, this has become practical at significant scale.&lt;/p&gt;
&lt;p&gt;But Kappa has real limitations. Reprocessing from raw events works well for recent data and low-cardinality dimensions. It breaks down when you need aggregations over long time ranges or across high-cardinality keys like per-user statistics across millions of users. Good luck deriving yearly rollups from raw events when the event log holds billions of entries. The throughput and parallelism requirements for a Kappa system can be substantial, and replaying months or years of history through a stream processor is expensive if it’s even feasible.&lt;/p&gt;
&lt;p&gt;The right choice depends on the use case. Kappa works well for systems where the time window is bounded, the cardinality is manageable, and you can afford the infrastructure to keep up with replay demands. Lambda remains the better fit when you need deep historical aggregations over high-cardinality data, where a batch layer can churn through the volume efficiently in a way that stream replay cannot.&lt;/p&gt;
&lt;h2 id=&quot;choosing-your-approach&quot; tabindex=&quot;-1&quot;&gt;Choosing your approach&lt;/h2&gt;
&lt;p&gt;For most teams, a purpose-built time series database is the right answer. TimescaleDB if you want to stay in the PostgreSQL ecosystem, ClickHouse for heavy analytics, InfluxDB or VictoriaMetrics for metrics and monitoring.&lt;/p&gt;
&lt;p&gt;Custom rollup pipelines still make sense when your data doesn’t fit neatly into the time series model, when you need full control over the aggregation logic, or when you’re operating at a scale where managed solutions become prohibitively expensive.&lt;/p&gt;
&lt;p&gt;Either way, the fundamentals don’t change. Pre-aggregate. Roll up into time buckets. Trade resolution for speed and storage. These patterns were solid in 2011 and they’re solid now. The tools just got a lot better at hiding the machinery.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <id>https://torstencurdt.com/tech/random-lines-from-large-files/</id>
    <title>Random Lines from Large Files</title>
    <link href="https://torstencurdt.com/tech/random-lines-from-large-files/"/>
    <updated>2011-05-13T00:00:00Z</updated>
    <content type="html">&lt;p&gt;When working with big data, taking samples is the only road to quick answers. Unfortunately that already poses a bigger hurdle than it should. Ask around how to get random lines from a file and you’ll most likely hear something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;shuf -n 10 file.txt
cat file.txt | sort --random-sort | head -n 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that passing the file as an argument instead of piping through &lt;code&gt;cat&lt;/code&gt; would avoid the &lt;a href=&quot;https://porkmail.org/era/unix/award&quot;&gt;useless use of cat&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;why-these-don-t-scale&quot; tabindex=&quot;-1&quot;&gt;Why these don’t scale&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;sort&lt;/code&gt; approach is the worst offender. It reads the entire file, shuffles all lines, and only then lets you pick from the top. For a 10 GB file that means sorting billions of lines just to grab 10.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;shuf&lt;/code&gt; is smarter but still not fast. Looking at the &lt;a href=&quot;https://github.com/coreutils/coreutils/blob/master/src/shuf.c&quot;&gt;source code&lt;/a&gt;, it reads the entire input sequentially. Even with &lt;code&gt;-n 1&lt;/code&gt; it processes every single line. Recent versions use reservoir sampling internally when combined with &lt;code&gt;-n&lt;/code&gt;, which at least keeps memory usage bounded. But it still has to scan through the whole file from start to finish.&lt;/p&gt;
&lt;p&gt;For a 10 GB file, that means waiting minutes for what should take milliseconds.&lt;/p&gt;
&lt;h2 id=&quot;seeking-instead-of-reading&quot; tabindex=&quot;-1&quot;&gt;Seeking instead of reading&lt;/h2&gt;
&lt;p&gt;If the file is on disk (not stdin), we can do something much faster. Instead of reading every line, we pick random byte positions, seek directly to them, skip to the next newline, and read the following complete line.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
import random

def random_lines(filename, n=10):
    size = os.path.getsize(filename)
    positions = sorted(random.randint(0, size - 1) for _ in range(n))

    lines = []
    with open(filename, &#39;rb&#39;) as f:
        for pos in positions:
            f.seek(max(0, pos - 1))
            f.readline()          # skip partial line
            line = f.readline()   # read full line
            if line:
                lines.append(line.decode().strip())
    return lines

for line in random_lines(&amp;quot;data.txt&amp;quot;):
    print(line)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sorting the positions before seeking means we sweep through the file in one direction, which is friendlier to the disk cache. The whole thing runs in O(n) seeks regardless of file size.&lt;/p&gt;
&lt;h2 id=&quot;the-catch-the-line-length-bias&quot; tabindex=&quot;-1&quot;&gt;The catch - the line length bias&lt;/h2&gt;
&lt;p&gt;This approach picks random byte positions, not random line positions. That means longer lines occupy more bytes and are more likely to be “hit” by a random position. If all lines are roughly the same length, the bias is negligible. If your file has a mix of 10-character and 10,000-character lines, the long lines will be heavily overrepresented.&lt;/p&gt;
&lt;p&gt;For quick exploratory sampling this usually doesn’t matter. But if statistical correctness is important, you need a different approach.&lt;/p&gt;
&lt;h2 id=&quot;reservoir-sampling&quot; tabindex=&quot;-1&quot;&gt;Reservoir sampling&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Reservoir_sampling&quot;&gt;Reservoir sampling&lt;/a&gt; (Algorithm R, published by &lt;a href=&quot;https://www.cs.umd.edu/~samir/498/vitter.pdf&quot;&gt;Jeffrey Vitter in 1985&lt;/a&gt;) is the theoretically correct way to get a uniform random sample from data of unknown size. It works in a single pass and uses O(k) memory, where k is the sample size.&lt;/p&gt;
&lt;p&gt;The idea is simple. Keep the first k lines in a “reservoir”. For every subsequent line j, generate a random number between 1 and j. If it falls within k, replace that slot in the reservoir. When you reach the end of the file, the reservoir contains a perfectly uniform sample.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import random

def reservoir_sample(filename, k=10):
    reservoir = []
    with open(filename) as f:
        for i, line in enumerate(f):
            if i &amp;lt; k:
                reservoir.append(line.strip())
            else:
                j = random.randint(0, i)
                if j &amp;lt; k:
                    reservoir[j] = line.strip()
    return reservoir
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The downside is obvious: it still reads every line in the file, just like &lt;code&gt;shuf&lt;/code&gt;. The advantage is that it’s guaranteed to produce a uniform sample regardless of line lengths, and it works on streams where you can’t seek.&lt;/p&gt;
&lt;h2 id=&quot;which-one-to-pick&quot; tabindex=&quot;-1&quot;&gt;Which one to pick&lt;/h2&gt;
&lt;p&gt;It depends on what you need.&lt;/p&gt;
&lt;p&gt;For quick sampling from large files on disk where line lengths are reasonably uniform, the seek-based approach is hard to beat. It runs in milliseconds on files of any size.&lt;/p&gt;
&lt;p&gt;For statistically correct uniform sampling, or when reading from a pipe or stream, reservoir sampling is the right choice. It’s slower but mathematically sound.&lt;/p&gt;
&lt;p&gt;For small files, just use &lt;code&gt;shuf -n 10&lt;/code&gt;. It’s simple and good enough.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <id>https://torstencurdt.com/tech/building-debian-packages-in-java/</id>
    <title>Building Debian Packages in Java</title>
    <link href="https://torstencurdt.com/tech/building-debian-packages-in-java/"/>
    <updated>2007-01-19T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Using native packaging formats to install Java applications leverages existing and well-tested infrastructure for software deployment. But one of the perks of Java has always been the cross-platform build. Using the standard native tools like &lt;code&gt;dpkg-deb&lt;/code&gt; to create those packages breaks the promise of a platform independent build.&lt;/p&gt;
&lt;p&gt;With the &lt;a href=&quot;https://github.com/tcurdt/jdeb&quot;&gt;jdeb&lt;/a&gt; project there is a way to create Debian packages directly from your build - whether you use &lt;a href=&quot;https://maven.apache.org/&quot;&gt;Maven&lt;/a&gt;, &lt;a href=&quot;https://gradle.org/&quot;&gt;Gradle&lt;/a&gt;, or &lt;a href=&quot;https://ant.apache.org/&quot;&gt;Ant&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Build Debian packages without native tools - on Linux, Windows, or macOS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The project has been &lt;a href=&quot;https://github.com/tcurdt/jdeb/blob/master/HISTORY.md&quot;&gt;maintained since 2007&lt;/a&gt; and is used by over 3,500 repositories on GitHub - including projects at Apache, Netflix, and Eclipse.&lt;/p&gt;
&lt;h2 id=&quot;why-native-packages-in-the-container-era&quot; tabindex=&quot;-1&quot;&gt;Why native packages in the container era?&lt;/h2&gt;
&lt;p&gt;With containers being everywhere, the question comes up whether native packages are still relevant. I think the answer is yes - for several reasons.&lt;/p&gt;
&lt;p&gt;First, even inside containers you often want proper package management. A clean &lt;code&gt;apt install&lt;/code&gt; with declared dependencies beats copying files around. System services, users, permissions, and config files are all things that packages handle well.&lt;/p&gt;
&lt;p&gt;Second, not everything runs in containers. Bare-metal deployments, embedded systems, appliances, and developer tooling all benefit from native packages. And even when containers are the delivery vehicle, building the underlying image from proper packages keeps things reproducible.&lt;/p&gt;
&lt;p&gt;You could of course use Docker to run the native packaging tools during the build. But that adds considerable complexity to what should be a straightforward Java build. The whole point of jdeb is to avoid that.&lt;/p&gt;
&lt;h2 id=&quot;the-control-file&quot; tabindex=&quot;-1&quot;&gt;The control file&lt;/h2&gt;
&lt;p&gt;The only requirement is a control file that provides metadata about the package. It declares things like name, version, and most importantly dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Package: [[name]]
Version: [[version]]
Section: misc
Priority: optional
Architecture: all
Description: [[description]]
Maintainer: Your Name &amp;lt;you@example.com&amp;gt;
Depends: default-jre (&amp;gt;= 11)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;jdeb also handles maintainer scripts (&lt;code&gt;preinst&lt;/code&gt;, &lt;code&gt;postinst&lt;/code&gt;, &lt;code&gt;prerm&lt;/code&gt;, &lt;code&gt;postrm&lt;/code&gt;) - just place them next to the control file in your control directory. These scripts are often the tricky part of packaging, and jdeb passes them through without getting in the way.&lt;/p&gt;
&lt;h2 id=&quot;maven&quot; tabindex=&quot;-1&quot;&gt;Maven&lt;/h2&gt;
&lt;p&gt;With Maven, jdeb attaches to the &lt;em&gt;package&lt;/em&gt; phase. A build creates the jar, then builds the &lt;code&gt;.deb&lt;/code&gt; and attaches it as a secondary artifact.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;plugin&amp;gt;
  &amp;lt;artifactId&amp;gt;jdeb&amp;lt;/artifactId&amp;gt;
  &amp;lt;groupId&amp;gt;org.vafer&amp;lt;/groupId&amp;gt;
  &amp;lt;version&amp;gt;1.14&amp;lt;/version&amp;gt;
  &amp;lt;executions&amp;gt;
    &amp;lt;execution&amp;gt;
      &amp;lt;phase&amp;gt;package&amp;lt;/phase&amp;gt;
      &amp;lt;goals&amp;gt;
        &amp;lt;goal&amp;gt;jdeb&amp;lt;/goal&amp;gt;
      &amp;lt;/goals&amp;gt;
      &amp;lt;configuration&amp;gt;
        &amp;lt;controlDir&amp;gt;${basedir}/src/deb/control&amp;lt;/controlDir&amp;gt;
        &amp;lt;dataSet&amp;gt;
          &amp;lt;data&amp;gt;
            &amp;lt;src&amp;gt;${project.build.directory}/${project.build.finalName}.jar&amp;lt;/src&amp;gt;
            &amp;lt;type&amp;gt;file&amp;lt;/type&amp;gt;
            &amp;lt;mapper&amp;gt;
              &amp;lt;type&amp;gt;perm&amp;lt;/type&amp;gt;
              &amp;lt;prefix&amp;gt;/usr/share/myapp/lib&amp;lt;/prefix&amp;gt;
              &amp;lt;user&amp;gt;loader&amp;lt;/user&amp;gt;
              &amp;lt;group&amp;gt;loader&amp;lt;/group&amp;gt;
              &amp;lt;filemode&amp;gt;640&amp;lt;/filemode&amp;gt;
            &amp;lt;/mapper&amp;gt;
          &amp;lt;/data&amp;gt;
        &amp;lt;/dataSet&amp;gt;
      &amp;lt;/configuration&amp;gt;
    &amp;lt;/execution&amp;gt;
  &amp;lt;/executions&amp;gt;
&amp;lt;/plugin&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;mvn package&lt;/code&gt; now produces both the jar and the &lt;code&gt;.deb&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;gradle&quot; tabindex=&quot;-1&quot;&gt;Gradle&lt;/h2&gt;
&lt;p&gt;jdeb itself does not ship a Gradle plugin, but Netflix’s &lt;a href=&quot;https://github.com/nebula-plugins/gradle-ospackage-plugin&quot;&gt;nebula ospackage plugin&lt;/a&gt; uses jdeb under the hood and integrates beautifully with Gradle’s copy spec.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;plugins {
  id &#39;com.netflix.nebula.ospackage&#39; version &#39;12.2.0&#39;
}

ospackage {
  packageName = &#39;myapp&#39;
  version = &#39;1.0&#39;
  release = &#39;1&#39;

  into &#39;/usr/share/myapp/lib&#39;

  from(jar.outputs.files) {
    fileMode = 0640
    user = &#39;loader&#39;
    permissionGroup = &#39;loader&#39;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;gradle buildDeb&lt;/code&gt; creates the package.&lt;/p&gt;
&lt;h2 id=&quot;ant&quot; tabindex=&quot;-1&quot;&gt;Ant&lt;/h2&gt;
&lt;p&gt;For Ant, jdeb provides a task directly. Specify the paths and provide the data that should get included.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;taskdef name=&amp;quot;deb&amp;quot;
  classname=&amp;quot;org.vafer.jdeb.ant.DebAntTask&amp;quot;
  classpath=&amp;quot;jdeb.jar&amp;quot; /&amp;gt;

&amp;lt;deb
  destfile=&amp;quot;${build.dir}/${ant.project.name}.deb&amp;quot;
  control=&amp;quot;${build.dir}/deb/control&amp;quot;
  verbose=&amp;quot;true&amp;quot;&amp;gt;
  &amp;lt;data type=&amp;quot;file&amp;quot;
    src=&amp;quot;${build.dir}/jar/${ant.project.name}-${version}.jar&amp;quot;&amp;gt;
    &amp;lt;mapper type=&amp;quot;perm&amp;quot; prefix=&amp;quot;/usr/share/jdeb/lib&amp;quot;/&amp;gt;
  &amp;lt;/data&amp;gt;
&amp;lt;/deb&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;github-actions&quot; tabindex=&quot;-1&quot;&gt;GitHub Actions&lt;/h2&gt;
&lt;p&gt;Since jdeb runs as part of your normal build, creating packages in CI is straightforward. No special actions or Docker containers needed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;- name: Build with Maven
  run: mvn package

- name: Upload .deb artifact
  uses: actions/upload-artifact@v4
  with:
    name: debian-package
    path: target/*.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. The &lt;code&gt;.deb&lt;/code&gt; gets built on whatever runner GitHub provides - Linux, macOS, or even Windows.&lt;/p&gt;
&lt;h2 id=&quot;alternatives&quot; tabindex=&quot;-1&quot;&gt;Alternatives&lt;/h2&gt;
&lt;p&gt;Naturally, jdeb is not the only option. Depending on your setup, these might be worth considering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/goreleaser/nfpm&quot;&gt;nfpm&lt;/a&gt; is a Go tool that builds &lt;code&gt;.deb&lt;/code&gt;, &lt;code&gt;.rpm&lt;/code&gt;, and &lt;code&gt;.apk&lt;/code&gt; packages from a simple YAML config. No native tools required. I have contributed to nfpm and it is a great choice for all kinds of projects.&lt;/li&gt;
&lt;li&gt;Native tools in CI. If your CI already runs on Debian, using &lt;code&gt;dpkg-deb&lt;/code&gt; directly is always an option. You lose the cross-platform build, but for some workflows that is fine.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nixos.org/&quot;&gt;Nix&lt;/a&gt; takes a fundamentally different approach to packaging and deployment. A bigger commitment but very powerful.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-started&quot; tabindex=&quot;-1&quot;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://github.com/tcurdt/jdeb/tree/master/docs&quot;&gt;documentation&lt;/a&gt; and the &lt;a href=&quot;https://github.com/tcurdt/jdeb/tree/master/src/examples&quot;&gt;examples&lt;/a&gt; on GitHub. Issues and discussions are welcome in the &lt;a href=&quot;https://github.com/tcurdt/jdeb&quot;&gt;repository&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <id>https://torstencurdt.com/tech/ditto-vs-zip/</id>
    <title>ditto vs zip vs tar on macOS</title>
    <link href="https://torstencurdt.com/tech/ditto-vs-zip/"/>
    <updated>2006-05-02T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Files on macOS carry more data than what you see in Finder. Beyond the file content there is metadata - and not all archiving tools preserve it. This has been a source of subtle bugs for decades.&lt;/p&gt;
&lt;h2 id=&quot;resource-forks-and-extended-attributes&quot; tabindex=&quot;-1&quot;&gt;Resource forks and extended attributes&lt;/h2&gt;
&lt;p&gt;Back in the classic Mac OS days, files had &lt;a href=&quot;https://en.wikipedia.org/wiki/Resource_fork&quot;&gt;resource forks&lt;/a&gt; - a second data stream that stored structured metadata like icons, dialog layouts, or code. This was a feature of the &lt;a href=&quot;https://en.wikipedia.org/wiki/HFS_Plus&quot;&gt;HFS&lt;/a&gt; file system and pretty unique to the Mac.&lt;/p&gt;
&lt;p&gt;When Apple moved to Mac OS X and eventually to &lt;a href=&quot;https://en.wikipedia.org/wiki/Apple_File_System&quot;&gt;APFS&lt;/a&gt;, resource forks didn’t really disappear. They evolved into &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_file_attributes&quot;&gt;extended attributes&lt;/a&gt;. Arbitrary key-value pairs attached to any file. APFS supports them natively, and macOS uses them everywhere.&lt;/p&gt;
&lt;p&gt;Some concrete examples of extended attributes you encounter daily:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;com.apple.quarantine&lt;/code&gt; is the flag that triggers “this file was downloaded from the internet” warnings&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.apple.metadata:kMDItemWhereFroms&lt;/code&gt; records which URL a file was downloaded from&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.apple.FinderInfo&lt;/code&gt; stores Finder tags and other Finder-specific metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can inspect them with the &lt;code&gt;xattr&lt;/code&gt; command line tool:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ xattr -l sample.txt
com.apple.quarantine: 0081;12345678;Safari;ABC123
user.custom: my-custom-value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other operating systems have similar concepts. Linux has extended attributes via &lt;code&gt;getfattr&lt;/code&gt; and &lt;code&gt;setfattr&lt;/code&gt; (used by SELinux for security labels, for instance). Windows has &lt;a href=&quot;https://en.wikipedia.org/wiki/NTFS#Alternate_data_streams_(ADS)&quot;&gt;alternate data streams&lt;/a&gt; on NTFS. But macOS relies on them more heavily than most, which is why archiving them matters.&lt;/p&gt;
&lt;h2 id=&quot;the-archiving-problem&quot; tabindex=&quot;-1&quot;&gt;The archiving problem&lt;/h2&gt;
&lt;p&gt;Not all archive tools treat extended attributes the same way. Checking the behaviour of &lt;code&gt;zip&lt;/code&gt;, &lt;code&gt;ditto&lt;/code&gt;, and &lt;code&gt;tar&lt;/code&gt; on macOS 15 (Sequoia) reveals some interesting differences.&lt;/p&gt;
&lt;h3 id=&quot;zip&quot; tabindex=&quot;-1&quot;&gt;zip&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ zip -r archive.zip testfolder/
$ unzip -l archive.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       12  02-23-2026 23:42   testfolder/sample.txt
---------                     -------
       12                     1 file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Standard &lt;code&gt;zip&lt;/code&gt; creates a clean, portable archive. But it &lt;strong&gt;drops extended attributes&lt;/strong&gt;. What you get back after extracting is the file content - nothing more.&lt;/p&gt;
&lt;h3 id=&quot;ditto&quot; tabindex=&quot;-1&quot;&gt;ditto&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ ditto -ck testfolder archive.zip
$ unzip -l archive.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       12  02-23-2026 23:42   sample.txt
      264  02-23-2026 23:42   ._sample.txt
---------                     -------
      276                     2 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ditto&lt;/code&gt; is what Finder uses when you right-click and choose “Compress”. It stores extended attributes in &lt;a href=&quot;https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats&quot;&gt;AppleDouble&lt;/a&gt; &lt;code&gt;._&lt;/code&gt; files alongside the actual content. On a Mac, extracting this archive restores the full metadata. This has been the default behavior since Mac OS X 10.4.&lt;/p&gt;
&lt;p&gt;The catch: when someone unpacks a &lt;code&gt;ditto&lt;/code&gt; archive on Linux or Windows, those &lt;code&gt;._&lt;/code&gt; files just show up as regular files. Annoying and unexpected for the non-Mac users.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;The &lt;code&gt;dot_clean&lt;/code&gt; command can merge or remove those &lt;code&gt;._&lt;/code&gt; files after extraction. And when sending archives to non-Mac users, &lt;code&gt;ditto -ck --norsrc&lt;/code&gt; creates a clean zip without the extra metadata.&lt;/p&gt;
&lt;/aside&gt;
&lt;h3 id=&quot;tar-bsdtar&quot; tabindex=&quot;-1&quot;&gt;tar (bsdtar)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ tar -cf archive.tar testfolder/
$ tar -tf archive.tar
testfolder/
testfolder/sample.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;tar&lt;/code&gt; that ships with macOS is actually &lt;a href=&quot;https://www.freebsd.org/cgi/man.cgi?query=bsdtar&quot;&gt;bsdtar&lt;/a&gt;, not GNU tar. And it preserves extended attributes by default - no special flags needed. The metadata is stored natively in the archive format.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# disable metadata explicitly if you don&#39;t want it
$ tar --no-mac-metadata -cf clean.tar testfolder/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes &lt;code&gt;tar&lt;/code&gt; a solid choice when you’re archiving on a Mac for use on a Mac. The metadata travels with the archive transparently and doesn’t produce any &lt;code&gt;._&lt;/code&gt; clutter.&lt;/p&gt;
&lt;h2 id=&quot;quick-comparison&quot; tabindex=&quot;-1&quot;&gt;Quick comparison&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Tool&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;xattrs&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Format&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Cross-platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;zip&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;no&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;zip&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;clean everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;ditto -ck&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;yes&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;zip (with &lt;code&gt;._&lt;/code&gt; files)&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;._&lt;/code&gt; files on non-Mac&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;tar&lt;/code&gt; (bsdtar)&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;yes&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;tar (native metadata)&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;only macOS bsdtar restores metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;tar --no-mac-metadata&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;no&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;tar&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;clean everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;when-to-use-what&quot; tabindex=&quot;-1&quot;&gt;When to use what&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Sharing with non-Mac users?&lt;/strong&gt; Use &lt;code&gt;zip&lt;/code&gt; or &lt;code&gt;tar --no-mac-metadata&lt;/code&gt;. The &lt;code&gt;._&lt;/code&gt; files from &lt;code&gt;ditto&lt;/code&gt; archives tend to cause confusion on the receiving end.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Archiving for yourself or other Mac users?&lt;/strong&gt; Use &lt;code&gt;ditto -ck&lt;/code&gt; or just &lt;code&gt;tar&lt;/code&gt;. Both preserve the full metadata. &lt;code&gt;ditto&lt;/code&gt; gives you a zip that Finder can preview, &lt;code&gt;tar&lt;/code&gt; is more natural for the command line.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backing up files where metadata matters?&lt;/strong&gt; &lt;code&gt;tar&lt;/code&gt; is the first choice. It preserves everything without the &lt;code&gt;._&lt;/code&gt; file overhead. Though for real backups you probably want &lt;a href=&quot;https://support.apple.com/en-us/104984&quot;&gt;Time Machine&lt;/a&gt; or &lt;code&gt;tmutil&lt;/code&gt;, which handle extended attributes as well.&lt;/p&gt;
&lt;p&gt;For the last 20 years the advice has been to use &lt;code&gt;ditto&lt;/code&gt; over &lt;code&gt;zip&lt;/code&gt;. That hasn’t changed. But it’s worth knowing that &lt;code&gt;tar&lt;/code&gt; on macOS has quietly become a solid alternative that avoids the trade-off entirely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;: Don’t use &lt;code&gt;zip&lt;/code&gt; if metadata matters. Use &lt;code&gt;ditto&lt;/code&gt; for zip archives, &lt;code&gt;tar&lt;/code&gt; for everything else.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <id>https://torstencurdt.com/tech/duff-device/</id>
    <title>The Duff Device</title>
    <link href="https://torstencurdt.com/tech/duff-device/"/>
    <updated>2006-01-10T00:00:00Z</updated>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Duff%27s_device&quot;&gt;Duff’s Device&lt;/a&gt; was discovered by Tom Duff in November 1983 while working at Lucasfilm. He needed to speed up a real-time animation program that was running 50% too slowly. The program had to copy data into the Programmed IO data register of an Evans &amp;amp; Sutherland Picture System II and the straightforward loop just wasn’t fast enough.&lt;/p&gt;
&lt;p&gt;What he came up with is one of the most famous pieces of C code ever written. It’s also one of the most confusing.&lt;/p&gt;
&lt;h2 id=&quot;the-idea&quot; tabindex=&quot;-1&quot;&gt;The idea&lt;/h2&gt;
&lt;p&gt;Let’s start with a simple loop that copies some memory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;for (int i = 0; i &amp;lt; len; i++) {
    *output++ = *input++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For longer loops, the overhead of checking the loop condition adds up. On every iteration the loop checks the counter against &lt;code&gt;len&lt;/code&gt;, increments it, and jumps back. That’s a lot of bookkeeping for a single copy operation.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Loop_unrolling&quot;&gt;Loop unrolling&lt;/a&gt; reduces this overhead by doing multiple operations per iteration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// assuming len is a multiple of 8
int n = len / 8;
do {
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
} while (--n &amp;gt; 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we only pay the loop overhead once for every 8 copies. But what if &lt;code&gt;len&lt;/code&gt; isn’t a multiple of 8? You’d need to handle the remainder separately. This is where Duff’s Device gets creative.&lt;/p&gt;
&lt;h2 id=&quot;the-trick&quot; tabindex=&quot;-1&quot;&gt;The trick&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int n = (len + 8 - 1) / 8;
switch (len % 8) {
    case 0: do { *output++ = *input++;
    case 7:      *output++ = *input++;
    case 6:      *output++ = *input++;
    case 5:      *output++ = *input++;
    case 4:      *output++ = *input++;
    case 3:      *output++ = *input++;
    case 2:      *output++ = *input++;
    case 1:      *output++ = *input++;
    } while (--n &amp;gt; 0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It looks like someone tricked the parser but it really is valid C. It exploits two features of the language: the fall-through behavior of &lt;code&gt;switch&lt;/code&gt; statements and the fact that &lt;code&gt;switch&lt;/code&gt; and &lt;code&gt;do-while&lt;/code&gt; can share a compound statement.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;switch&lt;/code&gt; acts as a jump table. On the first pass, it jumps to the right &lt;code&gt;case&lt;/code&gt; label based on the remainder of &lt;code&gt;len % 8&lt;/code&gt;, executing only the leftover copies. After that, the &lt;code&gt;do-while&lt;/code&gt; loop takes over, copying 8 elements at a time until done.&lt;/p&gt;
&lt;p&gt;As Duff himself &lt;a href=&quot;https://swtch.com/duffs-device/td-1983.txt&quot;&gt;wrote&lt;/a&gt;: “after 10 years of writing C there are still little corners that I haven’t explored fully.”&lt;/p&gt;
&lt;h2 id=&quot;making-it-readable&quot; tabindex=&quot;-1&quot;&gt;Making it readable&lt;/h2&gt;
&lt;p&gt;If we disentangle the two constructs, it becomes easier to follow.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int n = (len + 8 - 1) / 8;

// first: handle the remainder
switch (len % 8) {
    case 0: *output++ = *input++;
    case 7: *output++ = *input++;
    case 6: *output++ = *input++;
    case 5: *output++ = *input++;
    case 4: *output++ = *input++;
    case 3: *output++ = *input++;
    case 2: *output++ = *input++;
    case 1: *output++ = *input++;
}

// then: copy 8 at a time
while (--n &amp;gt; 0) {
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
    *output++ = *input++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same logic, no interleaved control structures. The &lt;code&gt;switch&lt;/code&gt; handles the odd remainder, and the &lt;code&gt;while&lt;/code&gt; loop does the rest in chunks of 8. In both versions, the loop overhead is reduced to 1/8 of the original.&lt;/p&gt;
&lt;p&gt;I chose a memory copy operation only for demonstration purposes. In practice, the standard C library &lt;code&gt;memcpy&lt;/code&gt; should always be preferred - it contains architecture-specific optimizations that will be faster than anything you write by hand.&lt;/p&gt;
&lt;h2 id=&quot;what-the-compiler-killed&quot; tabindex=&quot;-1&quot;&gt;What the compiler killed&lt;/h2&gt;
&lt;p&gt;Here’s the twist. In the late 1990s, the XFree86 project &lt;a href=&quot;https://en.wikipedia.org/wiki/Duff%27s_device#Performance&quot;&gt;removed Duff’s Device&lt;/a&gt; from their codebase. The result? The server got &lt;strong&gt;faster&lt;/strong&gt; and the binary got &lt;strong&gt;smaller&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The hand-unrolled code was actually preventing the compiler from applying its own optimizations. The interleaved &lt;code&gt;switch&lt;/code&gt; and &lt;code&gt;do-while&lt;/code&gt; created a control flow pattern that confused the optimizer. Simpler, straightforward loops gave the compiler room to do what it does best.&lt;/p&gt;
&lt;p&gt;Modern compilers like GCC and Clang perform loop unrolling automatically. The &lt;code&gt;-funroll-loops&lt;/code&gt; flag enables it, and with profile-guided optimization the compiler can make much better decisions about when and how much to unroll than a programmer writing code by hand. You can see exactly what the compiler does on &lt;a href=&quot;https://godbolt.org/&quot;&gt;Compiler Explorer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But compilers don’t just unroll loops. They also apply &lt;a href=&quot;https://en.wikipedia.org/wiki/SIMD&quot;&gt;SIMD vectorization&lt;/a&gt; - that’s processing multiple data elements in a single instruction. This is essentially what Duff’s Device was trying to achieve, but done at the hardware level. A single &lt;code&gt;MOVDQA&lt;/code&gt; instruction can copy 16 bytes at once. That’s a fundamentally different level of optimization than reducing branch overhead.&lt;/p&gt;
&lt;h2 id=&quot;clever-tricks-compilers-have-replaced&quot; tabindex=&quot;-1&quot;&gt;Clever tricks compilers have replaced&lt;/h2&gt;
&lt;p&gt;The Duff Device is not alone. Many hand-crafted optimizations that were once essential have been absorbed by compilers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strength reduction&lt;/strong&gt; replacing multiplication with bit shifts (&lt;code&gt;i * 4&lt;/code&gt; becomes &lt;code&gt;i &amp;lt;&amp;lt; 2&lt;/code&gt;). Compilers do this automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Common subexpression elimination&lt;/strong&gt; caching repeated calculations. The compiler tracks these across entire functions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constant folding&lt;/strong&gt; computing &lt;code&gt;3 * 60 * 24&lt;/code&gt; at compile time rather than runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dead code elimination&lt;/strong&gt; removing branches that can never execute.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Register allocation&lt;/strong&gt; the &lt;code&gt;register&lt;/code&gt; keyword in C is now ignored by most compilers because they do a better job of register allocation than programmers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What it comes down to: as compilers got smarter, hand-written optimizations went from helpful to harmful. Code that looks clever to a human can look opaque to an optimizer.&lt;/p&gt;
&lt;h2 id=&quot;when-to-optimize-by-hand&quot; tabindex=&quot;-1&quot;&gt;When to optimize by hand&lt;/h2&gt;
&lt;p&gt;There are still cases where manual optimization matters. Performance-critical inner loops in game engines, signal processing, and scientific computing sometimes benefit from hand-tuned SIMD intrinsics or carefully structured memory access patterns. But these are exceptions, not the rule.&lt;/p&gt;
&lt;p&gt;For most code, the best optimization strategy is to write clear, straightforward loops and let the compiler do its job. Measure first, optimize second - and check the compiler output before rewriting anything by hand.&lt;/p&gt;
&lt;p&gt;The Duff Device remains a wonderful piece of programming history. It teaches us about the dark corners of C, the evolution of compilers, and perhaps most importantly - that the clever hack you’re proud of today might be the thing slowing you down tomorrow.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <id>https://torstencurdt.com/tech/why-generated-api-docs-suck/</id>
    <title>Why Generated API Docs Suck</title>
    <link href="https://torstencurdt.com/tech/why-generated-api-docs-suck/"/>
    <updated>2005-03-23T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Over the years I’ve been exposed to a lot of code bases across many languages. And over time it has become painfully clear that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Generated API docs just suck. Not as a concept, but what most teams make out of them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The core problem of documentation is that it needs to be kept up to date. Having it separate from the code makes it really hard to maintain. So the idea of keeping docs close to the code isn’t bad at all. But somewhere along the way, teams confused “having documentation” with “having useful documentation.”&lt;/p&gt;
&lt;h2 id=&quot;the-boilerplate-epidemic&quot; tabindex=&quot;-1&quot;&gt;The boilerplate epidemic&lt;/h2&gt;
&lt;p&gt;Here’s a Java snippet that should look familiar to anyone who has worked on a larger code base.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
 * Construct an entry with only a name.
 *
 * @param name the entry name
 */
public FooEntry(String name) {
    this.name = name;
}

/**
 * Get this entry&#39;s name.
 *
 * @return This entry&#39;s name.
 */
public String getName() {
    return name;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What’s the benefit here? Every comment just restates what the code already says. It’s noise dressed up as documentation.&lt;/p&gt;
&lt;p&gt;This isn’t a Java problem though. TypeScript has the same disease.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;/**
 * Gets the user by ID.
 * @param id - The user ID.
 * @returns The user object.
 */
function getUser(id: string): User {
  return users.get(id);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go isn’t immune either.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// GetUser returns the user for the given id.
func GetUser(id string) *User {
    return users[id]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And even Rust, despite having one of the best documentation cultures, still attracts this pattern.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;/// Returns the name of the entry.
fn name(&amp;amp;self) -&amp;gt; &amp;amp;str {
    &amp;amp;self.name
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In all four cases the type system already tells you everything the comment does. The signature is the documentation. Adding a comment that just rephrases it creates maintenance burden with zero informational value.&lt;/p&gt;
&lt;h2 id=&quot;why-do-we-keep-doing-this&quot; tabindex=&quot;-1&quot;&gt;Why do we keep doing this?&lt;/h2&gt;
&lt;p&gt;For one, there’s a good chance developers didn’t write much of these docs in the first place. IDEs generate them. Linters enforce them. Checkstyle rules fail the build if they’re missing. And now AI assistants happily generate &lt;code&gt;/** Gets the name. @return the name */&lt;/code&gt; for every getter without anyone asking.&lt;/p&gt;
&lt;p&gt;The incentive is to satisfy the tooling, not the reader. That’s backwards.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you write documentation for the linter instead of the human, the human loses.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;what-good-documentation-looks-like&quot; tabindex=&quot;-1&quot;&gt;What good documentation looks like&lt;/h2&gt;
&lt;p&gt;The best documentation tells a story. It explains the &lt;em&gt;why&lt;/em&gt;, outlines the important parts of the &lt;em&gt;how&lt;/em&gt;, and gives &lt;em&gt;notice&lt;/em&gt; of anything that might surprise you. Method signatures already cover the &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here’s a better version of that Java class.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
 * Represents a single entry in a Foo archive.
 *
 * Entries are created during archive construction and
 * written sequentially. The header format follows the
 * Foo specification v2.1, section 4.3.
 *
 * Instances are not thread-safe. If shared between threads,
 * external synchronization is required.
 */
public class FooEntry implements ArchiveEntry {

    private String name = &amp;quot;&amp;quot;;

    public FooEntry(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void writeEntryHeader(OutputStream out)
        throws IOException {
        out.write(1);
        out.write(2);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The class-level comment does the heavy lifting. It explains what this thing &lt;em&gt;is&lt;/em&gt;, where it fits in the bigger picture, and what you need to watch out for. The individual methods don’t need comments because their names and signatures are self-explanatory.&lt;/p&gt;
&lt;p&gt;Focus on self-explanatory class, function and variable names. Often that’s even better than comments because the explanation is visible not only at declaration time but with every use. That’s the path to &lt;a href=&quot;https://martinfowler.com/bliki/CodeAsDocumentation.html&quot;&gt;code as documentation&lt;/a&gt;. A lot of comments then become duplication and can be removed. Less visual clutter, more signal.&lt;/p&gt;
&lt;h2 id=&quot;languages-that-got-it-right&quot; tabindex=&quot;-1&quot;&gt;Languages that got it right&lt;/h2&gt;
&lt;p&gt;Some ecosystems have found clever ways to make documentation both useful and trustworthy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rust’s doc-tests&lt;/strong&gt; let you write examples in documentation comments that the compiler &lt;a href=&quot;https://doc.rust-lang.org/rustdoc/documentation-tests.html&quot;&gt;actually runs as tests&lt;/a&gt;. If the example breaks, the build breaks. This means documentation stays in sync with the code by force, not by discipline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Go’s testable examples&lt;/strong&gt; follow a &lt;a href=&quot;https://go.dev/blog/examples&quot;&gt;similar philosophy&lt;/a&gt;. You write functions named &lt;code&gt;ExampleFoo&lt;/code&gt; in test files. They show up in the generated docs and run as part of the test suite. The output is verified against a comment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Elixir’s doctests&lt;/strong&gt; take this even further. Examples in &lt;code&gt;@doc&lt;/code&gt; attributes are &lt;a href=&quot;https://ex-unit.hexdocs.pm/ExUnit.DocTest.html&quot;&gt;executed as part of the test suite&lt;/a&gt;. The documentation &lt;em&gt;is&lt;/em&gt; the test. It’s the closest thing to documentation that can’t go stale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python’s doctest&lt;/strong&gt; module has been doing this &lt;a href=&quot;https://docs.python.org/3/library/doctest.html&quot;&gt;since 1999&lt;/a&gt;. Interactive examples in docstrings get extracted and verified. The idea is old but the principle is timeless: documentation that gets tested stays correct.&lt;/p&gt;
&lt;p&gt;What all of these have in common is that documentation must be &lt;em&gt;verified&lt;/em&gt;, not just &lt;em&gt;present&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;documentation-needs-structure&quot; tabindex=&quot;-1&quot;&gt;Documentation needs structure&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.divio.com/documentation-system/&quot;&gt;Divio documentation system&lt;/a&gt; makes a useful distinction between four types of documentation: tutorials, how-to guides, reference, and explanation. Most auto-generated docs only cover reference. That’s the least useful type when you’re trying to understand a new codebase.&lt;/p&gt;
&lt;p&gt;What developers actually need when they encounter unfamiliar code is explanation and context. Why does this module exist? How does it fit into the architecture? What are the trade-offs?&lt;/p&gt;
&lt;p&gt;Projects like &lt;a href=&quot;https://docs.djangoproject.com/&quot;&gt;Django&lt;/a&gt; and &lt;a href=&quot;https://docs.stripe.com/payments-api/tour&quot;&gt;Stripe&lt;/a&gt; are known for documentation that reads like a guided tour, not a phone book. Elixir’s ecosystem has built this into its culture. Modules in Phoenix often start with &lt;a href=&quot;https://fly.io/phoenix-files/elixir-docs-are-built-different/&quot;&gt;pages of discussion&lt;/a&gt; about what something is, how to use it, and what to watch out for. The API reference comes after.&lt;/p&gt;
&lt;h2 id=&quot;ai-won-t-save-us&quot; tabindex=&quot;-1&quot;&gt;AI won’t save us&lt;/h2&gt;
&lt;p&gt;AI coding assistants can generate documentation at scale. But they tend to produce exactly the kind of boilerplate this article argues against. They’re very good at restating what code does. They’re not good at explaining why it exists or how it fits into the bigger picture.&lt;/p&gt;
&lt;p&gt;That said, AI can help with &lt;em&gt;reading&lt;/em&gt; and understanding undocumented code. It can summarize, explain patterns, and answer questions about a codebase in ways that static docs never could. But even AI needs to “understand” the code the same way a human does. If there’s no narrative structure, no explanation of intent, the AI is guessing just like the next developer would be.&lt;/p&gt;
&lt;h2 id=&quot;write-for-the-reader&quot; tabindex=&quot;-1&quot;&gt;Write for the reader&lt;/h2&gt;
&lt;p&gt;First and foremost, empathize with the person reading and trying to understand your code.&lt;/p&gt;
&lt;p&gt;If you enforce documentation through linting, do it only for public APIs and focus on class-level or module-level docs. Nobody benefits from a mandatory &lt;code&gt;@param name the name&lt;/code&gt; on every setter.&lt;/p&gt;
&lt;p&gt;Write documentation at the highest useful level. Explain the module, not the getter. Tell the story of why this code exists. If you want to enforce documentation quality, verify your examples compile and run. Rust, Go, Elixir and Python have shown that this works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The best documentation isn’t the most complete. It’s the most useful.&lt;/strong&gt;&lt;/p&gt;
</content>
  </entry>
</feed>
