書くネタはあるのに、書かない
個人開発のリポジトリが8つある。それぞれに設計を決めた記録も、踏んだ失敗もある。ネタが無いわけではない。
それでも書かない。書きはじめるまでのコストが高いからだ。何を題材にするか決めて、どこまで社外に出していいか判断して、体裁を整えて、投稿する。この一連が面倒で、毎回そこで止まっていた。
なので、そこを全部パイプラインにした。5段になった。
候補抽出 → 執筆 → 公開前レビュー → 公開計画 → 自動公開。
作ってみて分かったのは、難しいのは文章を書くところではなかったということだ。詰まったのは全部その外側だった。
候補を探すところは、機械のほうが向いていた
最初の段は、各リポジトリの git 履歴とドキュメントを読んで「記事になりそうなもの」を拾い出す。
スコアの付け方は素朴だ。設計ドキュメントの分量と見出しの数。関連するコミットがまとまっているか。コミット本文が詳しく書かれていて意図が追えるか。複数日にまたがって続いた作業か。fix や security や retry のようなキーワードが出てくるか。
賢いことは何もしていないのに、これが効いた。人間は直近に触ったものばかり思い出す。 半年前に丸2日かけて潰した問題は、きれいに忘れている。機械は履歴を等しく見るので、忘れていたものが上位に出てくる。
実際、いま書きためている記事の半分くらいは、この抽出で「そういえばこれがあった」と思い出したものだ。
公開前スキャンが本体だった
作る前は3段目を軽く見ていた。実際にはここが一番重要で、一番設計に悩んだ。
個人開発とはいえ、リポジトリの中身には出したくないものが混ざる。自宅の IP アドレス、ルーターの MAC アドレス、家族の名前、実在しないサーバーへの古い手順、API キーの置き場所。ドキュメントには平気で書いてある。書いた時点では外に出す前提がないからだ。
なのでスキャンは公開を止める権限を持つようにした。検出があれば投稿処理そのものが中止される。フラグで迂回する経路は作っていない。「今回だけ通す」を作ると、必ずそれが常用される。
検出ルールは20個弱で、置換候補の辞書と組み合わせている。ここは「うるさすぎる」に寄せた。誤検出で止まるのは自分が確認すれば済むが、見逃しは取り返しがつかない。
同じ層に「未完成の記事を出さない」検査も入れた。やり残しを示すマーカーが本文に残っている、雛形の HTML コメントが消えていない、本文が規定の文字数に満たない。これで書きかけが自動投稿される事故を防いでいる。実際に1本、マーカーが残ったまま公開時刻を迎えて、正しく止まった。
白状すると、この記事も一度この検査に止められた。検査の説明をするためにマーカーの名前をそのまま本文に書いたら、書きかけと判定された。単語が含まれるかどうかしか見ていないので当然ではある。自分で書いた検査に、その検査を説明する記事が引っかかる。 文言を変えて通したが、誤検出をうるさい側に寄せると決めた以上、これは正しい動作だ。
launchd が、黙って何もしていなかった
自動公開は macOS の launchd で毎時動かしている。ここで2回転んだ。
1回目。ジョブがまったく動いていないのに、エラーも出ていない。調べたら、リポジトリを ~/Documents の下に置いていたのが原因だった。ここは macOS のプライバシー保護の対象で、バックグラウンドのプロセスからのアクセスには明示的な許可がいる。対話的なターミナルからは動くので、手で叩いている限り一生気づかない。
これはリポジトリをホーム直下へ移して解決した。ついでに、plist から直接リポジトリ内のスクリプトを起動するのをやめた。保護に弾かれるとログに理由すら残らないので、保護対象の外に小さな踏み台スクリプトを置いて、そこを経由させている。こうすると「弾かれた」という事実だけはログに残る。
2回目は、成功の定義の問題だった。ジョブの終了コードは正常なのに、実際には何も投稿されていない日があった。公開対象が無ければ何もせずに正常終了するので、「正常終了」と「仕事をした」が区別できない。いまは何本投稿したかを必ずログに残している。
このあたりは、自分が監視について書いた記事とまったく同じ失敗を、自分の自動化でやり直しただけだった。
Qiita の投稿数制限で25時間止まった
一番きれいに転んだのがここだ。
動作確認のつもりで手元から3本連続で投稿した。4秒で3本。そこから Qiita の投稿数制限に引っかかって、丸1日投稿できなくなった。
{"message":"サービスの安定運用のため、一定時間内の投稿数を制限させて
いただいております。…","type":"too_many_requests"}
厄介なのは、この制限には解除時刻が付いてこないことだった。Retry-After は返ってこない。レスポンスヘッダに乗っている残り回数の情報は、1時間あたりの API 呼び出し回数の枠で、こちらはほぼ使っていない。別の制限なので何の手がかりにもならない。
毎時リトライを続けて、解除されたのは約25時間後だった。最後の成功投稿からちょうど1日だ。その間ずっと、記事は scheduled のまま残して次回に回す作りにしていたので、解除された瞬間に自動で投稿されて復帰した。ここは設計しておいてよかった。
いまは1日1本しか投稿しない。1回の実行で1本という上限と、公開予定日を1日ずつずらす運用の両方でそれを担保している。外部サービスに投げる自動化で、まとめて投げていいことは何もない。
ついでに、この件で自分のコードのバグも1つ見つかった。制限を検出するための文言の照合が、実際のメッセージと1文字違っていて一度も一致していなかった。別の文言がたまたま拾っていたので表面化していなかっただけだった。
自動化して分かったこと
書く作業そのものは、たしかに楽になった。ただ効いたのは文章を生成する部分ではなく、判断を1箇所に固定したことのほうだと思う。
出していいかどうかは毎回スキャンが決める。いつ出すかは公開計画が決める。何を書くかは候補のスコアが決める。自分が毎回ゼロから迷わなくていい。迷わないから続く。
逆に、自動化しないと決めたところもある。どの候補を実際に記事にするか。構成をどうするか。この2つは残した。ここを機械に任せると、どれも似た形の記事になる。
そして一番の収穫は、自分の失敗が資産として扱われるようになったことだった。以前は、踏んだ失敗はその場で直して終わりだった。いまは候補抽出が fix のコミットを高く拾ってくるので、失敗するほど書くネタが増える。この記事自体がそうで、投稿制限に引っかかった話も、launchd が黙っていた話も、詰まらなければ書くことがなかった。
作ってよかったかというと、よかった。ただし理由は「楽になったから」ではなく、面倒で止まっていた場所が、面倒でなくなったからだ。