コードを自分の手で打て
(haskellforall.com)- コードを自分の手でタイピングし、記憶を頼りに再現する訓練は、コピーよりも理解と記憶をさらに厳密に検証してくれる
- Freecoding とは、構文、型、名前を頭の中に保持したまま、文字単位でコードを書く能力であり、ツールの時代でも必要である
- 構文 は高水準の思考を妨げるノイズではなく、精密な意味を圧縮してより高いレベルの思考を可能にする
- 型とスキーマ をその場しのぎに参照するだけではシステム設計が曖昧になり、型モデルを理解していないと
as anyのような回避が増える - 名前の記憶 と先行作業への理解が不足すると、エージェントは重複実装を作りやすくなり、出力やテストのレビューも難しくなる
なぜコードを自分で打つべきなのか
- Zed Shaw の “Learn X the hard way” コースでは、サンプルをコピー&ペーストせず自分で入力するよう勧めており、最近では練習を終えた後に一度削除し、記憶を頼りに再構築する方法をさらに強く勧めている
- 内容を能動的に生成すると、同じ内容を受動的に消費するときより理解が深まる現象は、認知心理学で 生成効果 と呼ばれる
- Richard Feynman の「自分が作れないものは理解していない」という言葉のように、記憶をもとにコードを作り直す訓練は、理解と記憶を同時に試す
- エージェントコーディングの時代でも、開発ツールを使うなという意味ではないが、ときにはツールの利便性なしに 文字単位でコード を書く能力を育てる必要がある
- この訓練は、構文、型、名前のようなプログラミングの細部を頭の中に保持し、「freecoding」できるようにすることに焦点を当てている
Freecoding と頭の中の細部知識
- Freecoding は、記憶を頼りにコードを自分で書ける能力であり、構文・型・名前のような基本要素を頭の中に保持していなければ成り立たない
-
構文と構造
- キーワード、句読点、言語構成要素に慣れている必要がある
- 単なる文法暗記ではなく、コードの形を正確に思い浮かべる能力と結びついている
-
型とスキーマ
- 型システムとデータモデルに慣れ、自然に扱える必要がある
- プロジェクトのテーブル、カラム、リレーション、型構造を毎回参照する程度にとどまると、システムレベルの設計は難しくなる
-
名前
- 関数、メソッド、クラス、import、ファイル、パッケージ名を正確に思い浮かべられなければならない
- プロジェクトや依存関係が変われば、この知識もそれに合わせて最新の状態に保つ必要がある
- 頭の中に思い描いたコードを一定の精度でタイピングできないなら、実際にはそれを明確に理解しているのではなく、理解していると思い込んでいる状態に近い
- 英語はコードほど精密な言語ではなく、十分に詳細な仕様は結局コードに近づく、という別記事 A sufficiently detailed spec is code にもつながる
構文は高水準の思考を可能にする
- IDE やコーディングエージェントが構文を整えてくれるとしても、構文を自分で扱う能力は依然として重要である
- 括弧を合わせるのに苦労するなら、他人の論理的な前提と結論を流暢につなぐ能力にも疑問が生じうる
- 細部を無視する態度は、語の意味を明確に吟味するよりも雰囲気でやり取りする 機能的非識字 につながりうる
- LLM プロンプトの例は、一見もっともらしく見えても、注意深く読むと相反する指示を同時に含んでいる
- 「上に列挙された skill に含まれない外部ツールや代替案を提案してはならない」
- 「作業が利用可能な skill を超える能力を必要とするなら、そう伝えよ」
- 小さな文法・綴り・構造を正確に扱う能力と、大きな全体像を正確に理解する能力は切り離せない
- 構文 は高水準の思考を妨げる細部ではなく、より高い水準の思考を圧縮し、可能にする精神的な道具である
- 自然言語での説明より、型注釈のほうが精密かつ簡潔なことがある
xはオブジェクトの配列であり、各オブジェクトには文字列を格納する必須のdomainプロパティと、数値を格納する省略可能なportプロパティがあるx : { domain: string, port?: number }[]
型とスキーマはシステム設計の核心的な手がかり
- Fred Brooks は The Mythical Man-Month で、テーブルを見せればフローチャートがなくてもたいてい構造は明らかになると述べている
- データベースを使うなら、プロジェクトのテーブル、カラム名、リレーションを完全に把握しておくべきである
- この情報を必要なときにその場しのぎで調べるのではなく、あらかじめ把握して頭の中に保持してこそ、効果的な システムレベル設計 が可能になる
- こうした努力をしなければ、思考が混乱しているのと同じだけ、データベーススキーマも重複や類似データの多い非正規化構造になりやすい
- Rust や Haskell のような強い型付け言語の経験は、型に対する強いメンタルモデルの利点をよく示してくれる
- 正確な「頭の中の型チェッカー」や「頭の中の borrow checker」を育てる訓練は、弱い型付け言語を使うときにも役立つ
- こうした抽象的な推論の筋肉を使わなければ、不可能な状態を表現不可能にする のようなデータモデリングの基本すら見落としやすい
- 型がどう噛み合っているかを理解していないと、TypeScript のコードに
as anyをばらまくことになる - 正常系で型について考える不便さに耐えられないなら、型エラーをデバッグする異常系はなおさら耐えがたい
- Haskeller や Rustacean が常により良いコードを書くという意味ではないが、他の条件が同じなら型に堪能であることは助けになる
名前を覚えてこそ再利用できる
- プロジェクトや依存関係でよく使う関数、メソッド、クラス、import、パッケージ、ファイル名は、すぐ思い浮かべられるべきである
- これは、すでに作られているもの、すなわち 先行作業 (prior art) に慣れていなければならない、というより一般的な原則の特殊形である
- 多くの人がコーディングエージェントに頼る理由は、望む機能をすでに実現している再利用可能な先行作業を知らないからであり、その結果エージェントに車輪の再発明をさせてしまう
- SaaS boilerplate projects の存在を知らなければ、エージェントが最初からスキャフォールディングしなければならないと思いがちである
- この目的のために作られた実績あるオープンソースプロジェクトを複製するほうが、エージェントに同じ作業を依頼するより速く、安く、信頼できる
- 誰かが LLM は数年以内にブラウザ全体を作れるようになると予測したとき、Chromium を fork すれば今日すでに可能であり、修正や保守も容易だという反論が成り立つ
- 同じプロジェクトや社内でのコード再利用においても、名前の記憶は重要である
- 何を探すべきか分からなければ、同僚の仕事の上に積み上げることはできない
エージェント出力のレビューには人間の理解が必要
- エージェントは名前の探索やコード生成を助けてくれるが、その出力を意味のある形でレビューできるかが新たな問題になる
- 既存機能を知らなければ、エージェントが機能を重複実装しているか判断しにくい
- エージェントよりコードを理解できていない状態では、エージェント出力の品質も適切にレビューしにくい
- エージェントにテスト生成を依頼することはできるが、生成されたテストを注意深く見なければ、テスト自体が無意味なコードになりうる
- 実際の例では、実装コードを呼ばずに配列や文字列へ直接
someやincludesを適用して、期待値だけを確認するテストだったdescribe("abort detection logic", () => { it("detects aborted stopReason in messages", () => { const messages = [ { role: "assistant", stopReason: "aborted", content: [] }, ]; const isAborted = messages.some((m: any) => m.stopReason === "aborted"); expect(isAborted).toBe(true); }); it("detects abort in error string", () => { const error = "The operation was aborted"; const isAborted = error.includes("abort"); expect(isAborted).toBe(true); }); it("does not false-positive on normal errors", () => { const error = "Network timeout"; const isAborted = error.includes("abort"); expect(isAborted).toBe(false); }); it("does not false-positive on normal stop reasons", () => { const messages = [ { role: "assistant", stopReason: "stop", content: [] }, ]; const isAborted = messages.some((m: any) => m.stopReason === "aborted"); expect(isAborted).toBe(false); }); }); - ソフトウェア開発には日常的な摩擦が多く、名前を覚えるという小さな摩擦を越えない選択をすると、テストをレビューするというより大きな摩擦も越えられなくなる
- コーディングエージェントはユーザーの指示を主要な文脈として扱うため、知的に怠惰なユーザーはエージェントも同じように怠惰な方向へ導きうる
粘り強さと熟練は切り離せない
- Eustress は有益なストレスであり、新しく難しいことへ自分を追い込むと、小さな不快への耐性がつき、より大きな不快も乗り越えられるようになる
- 常に不快を避けていると、無力感と挫折が強まる悪循環に陥る
- ある領域で精密さ、記憶、構造的思考を鍛えると、他の領域の能力も一緒に良くなる
- LLM が訓練データから一般化するように、人間もある領域で鍛えた習慣や能力を別の領域へ一般化する
- ソフトウェア開発は本質的に、快適な領域から定期的に出ることを含んでおり、関連記事 Software engineers are not (and should not be) technicians と同じ文脈にある
1件のコメント
Lobste.rs のコメント
完全に同意。練習問題を終えたあと、今やったことを消して、記憶だけでやり直してみることは本当に重要
行き詰まったらヒントを見るのはいいけれど、たった今やった過程をできるだけ記憶から再構成しようとする習慣ほど大事なものはあまりない
コミットメッセージとドキュメントは自分でタイプしたほうがいい
プログラマはこういう文章を書くのを嫌がって、「LLM に書かせればいいだろ」となりがちだけど、自分で書くとユーザー体験と実装上の意思決定に一か所で向き合うことになる。「X をするには
-Yを渡す……ちょっと待て、これが最善か?」「これは Y で X を直す……でも Z でもできるのでは?」みたいな見直しが生まれる「括弧の対応を取るのに苦労するなら、他人の論理的前提と結論をどれだけ流暢につなげられるのか疑わしい」というくだりで笑ったけど、ちょっと図星でもあった
弁解すると、関数がたまに大きくなりすぎることはある
理解していないバイブコーディングのゴミを直すのも、実際には学ぶにはいい方法だ。たくさんタイプすることになるし、真空状態ではなく実際の文脈の中で作業することになる
それを手で書き直しながら、この 8 か月で錆びついていたプログラミングの勘を取り戻す機会にした。書き直した結果は気に入っているし、実際によりよく動く。LLM は今でも説明したり、詰まった箇所をほぐしたりするのには有用だけれど、コードベース全体を自分が把握しているという感覚のほうがずっといい
「列挙された能力にない外部ツールや代替案は絶対に提案するな。作業に利用可能な能力を超える機能が必要なら、そう言え」というプロンプトが、なぜ互いに逆向きの指示なのかがよく分からない
「提供された能力だけではこれはできません」と言うのは、どちらの指示とも整合している。最後の文が「何の能力が必要かを言え」だったなら衝突するだろうけど、今の文面だけを見る限り矛盾は見えない
2つ目の文は、モデルに必要な外部ツールや代替案の存在を否定的に示唆させるのではなく、構成的に存在を肯定させるよう指示しているように読める
「機能的に明瞭に表現できない人は、ほぼ例外なくスペルや文法的に正しい文を書くのも苦手だ」という言い方は、ディスレクシアがある人や、単に異なる考え方をする人に対するかなり粗い偏見に見える
もちろん、スペルや文法が苦手だからといって、即座に機能的に明瞭でないと主張しているわけではないのだろうが、どちらにしても寛容さに欠ける表現だ
もっと建設的に見るなら、ノードエディタを考えてみるといい。ノードベースのシステムは現在の実装に問題が多いが、うまく作ればプログラムの書き方を制御して、一部の構文エラーを最初から排除できる。たとえば、あるノードが文字列を受け取るなら、そこに数値を渡すことはできない。ビルド時や実行時に制約を強制するからではなく、そもそもその場所に数値を「言えない」ようにするからだ。誤ったループ範囲、引数の個数ミス、括弧の不一致のようなものも、構造上間違えられないようにできる
ある制約を構造的に強制するツールでソフトウェアを書くからといって、その制約を理解していないことにはならない。単に、その種のエラーをうっかり入力してしまう心配を減らしてくれるということだ。関数の波括弧の数や論理ブロックのインデントといった構文の細部をツールが面倒見てくれないと不満を感じるからといって、なぜその構文に気を配る必要があるのかを理解していないことにもならない
ノードシステムを例に出したのは、LLM の「投げて忘れる」感じや、頭を止めたバイブコーディングの要素を取り除き、構文の問題だけを切り分けて見せるからだ。LLM を外して考えると、構文に焦点を当てる議論はかなり弱くなる
文章の残りの部分には同意するが、構文能力を誰かのコード理解力や良いコードを書く能力の根本的な指標のように扱うのは、かなり間違った方向だと思う
善意のフィードバックだという点も理解しているし、防御的に見えたいわけでもない。そういう寛容さのない主張に聞こえないよう努力したつもりだが、同じ内容をよりよく伝えられる別の表現や構成があるなら歓迎したい
ただ、その要点自体を削除したいわけではない。伝えたい核心は、人が精神的な不快を経験的に回避し始めると、たいていスペルと文法が最初に崩れる、ということだ。ディスレクシアのある人たちがその過程で不利益を受けないように、この主張を調整する方法はあると思う