朝いちばんに使う道具は、だいたい静かです。通知を鳴らしません。立派なダッシュボードもありません。ただ、必要なものだけを出して終わります。私は最近、その手の道具をGoで書くことが増えました。気づくと毎朝叩いているのも、そういう小さなCLIです。
毎朝使う道具は、機能より先に癖が要る
以前、ダウンロードフォルダの更新ファイルを新しい順に5件だけ出すGoのCLIを書きました。30分くらいで形になった雑なものです。ですが、これが妙に残りました。朝の作業前に一回だけ叩くと、昨日の自分が何を落として、何を放置したのかがすぐ見えます。ファイルマネージャーを開いて探すより、この一発のほうが頭が起きます。
こういう道具は、多機能なほうが偉いわけではありません。むしろ逆です。フィルタも色分けも設定画面も足さず、5件だけ出して終わるから残ります。毎日触るものは、便利さより先に癖の軽さが効きます。朝はまだ判断力が鈍いので、選択肢を増やされるとそれだけでだるいです。
Goだと雑な道具が最後まで道具のままで済む
この手のCLIを書くとき、私はGoへ戻りがちです。理由は単純で、単体バイナリで終われるからです。依存関係を思い出す必要がありませんし、数か月後に触り直しても気分が重くなりません。小さな道具ほど、導入より削除が簡単なほうが信用できます。
もうひとつ、Goの標準ライブラリはこういう用途で変に気取っていません。ディレクトリを読み、更新時刻で並べて、標準出力へ流します。それだけなら、余計な抽象化を持ち込まずに済みます。私はフレームワークの都合に文章まで合わせる感じが昔から苦手です。CLIくらいは、書いた人の手癖がそのまま残るほうが落ち着きます。
試した最小版はこんな形です。
package main
import (
"fmt"
"os"
"path/filepath"
"sort"
)
type entry struct {
name string
mod int64
}
func main() {
dir := "."
if len(os.Args) > 1 {
dir = os.Args[1]
}
items, err := os.ReadDir(dir)
if err != nil {
panic(err)
}
var entries []entry
for _, item := range items {
info, err := item.Info()
if err != nil || info.IsDir() {
continue
}
entries = append(entries, entry{
name: filepath.Join(dir, item.Name()),
mod: info.ModTime().Unix(),
})
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].mod > entries[j].mod
})
limit := 5
if len(entries) < limit {
limit = len(entries)
}
for _, e := range entries[:limit] {
fmt.Println(e.name)
}
}
派手ではありません。ただ、これで十分です。Goはこういう用途だと妙に強いです。現代のC言語っぽいと言いたくなるのも、たぶんこの感触のせいです。余計なものを背負わずに、OSに近いところの処理をそのまま短く書けます。
AIに任せるのは最初の5分だけでいい
最近は、この程度のCLIならAIに雛形だけ出させることもあります。flagの定義や設定ファイルの読み込みみたいな、書いていて別に楽しくないところです。そこは任せて構いません。ですが、毎朝使う道具の芯まで渡す気にはなれません。
本当に残る道具は、最後に自分で削った部分に癖が出ます。出力は3列にしません。ログも増やしすぎません。件数は5件で止めます。そういう雑な判断が、毎日の使い心地を決めます。非エンジニアがAIに丸投げしても道具が手になじまないのは、たぶんその最後の雑味を自分で引き受けていないからです。
プログラミングがしんどくなる瞬間は、周辺の仕組みばかり増えて、考える前に設定を触らされるときです。だから私は、小さいCLIではむしろ逆をやりたくなります。面倒な定型だけAIに片づけてもらって、残りは自分で考えます。そのほうが道具にちゃんと自分が残ります。
今も朝に叩く道具は、完成度でいえばかなり低いです。ですが、毎日使っている時点で負けていません。立派なアプリより、静かな標準出力のほうを信用する朝があります。そういう朝に残るものは、だいたい小さくて、Goで書けて、余計な顔をしていません。