ソフトウェアを素早く開発する私なりの方法
(evanhahn.com)- 完璧さと速度のバランスは簡単ではないが、状況に合った適切な品質と納期順守が重要
- まず下書き(ラフドラフト)として開発を進め、その後でコード品質を改善するやり方が効果的
- 要件を緩和したり過剰な要求を減らしたりすると、速度と効率を高められる
- 気の散りを避け、小さな単位で頻繁にコミットし、核心に集中する習慣が必要
- 迅速な開発に役立ったコードを読む力、データモデリング、スクリプティング、デバッグ、純粋関数志向などの具体的なスキルがある
「どれだけ良いコードであるべきか?」— 品質基準と現実的な選択
- 初期のころはすべてのコードが完璧であってほしいと思っていた
- すべての関数が徹底的にテストされ、変数名は洗練され、抽象化は明確で、バグが一切ないコードを夢見ていた
- しかし時間がたつにつれて、**「正解はない」**という現実を学んだ
- 状況によって求められるコード品質は異なる
- 24時間ゲームジャム: 完成したコードが必ずしもきれいであったりバグゼロであったりする必要はない
- 限られた時間内に動く成果物を作ることのほうが重要
- ペースメーカーのソフトウェア: たった一つのミスが人命を脅かしかねないため
- 最高水準の信頼性と安全性が必須
- ほとんどのプロジェクトはこの両極端の間にある
- ある会社は短納期を求めるため、多少のバグは許容する
- あるプロジェクトは高い品質を求めるが、スケジュールには余裕がある
- 実務ではこのバランスを見極める能力が重要
- まずはチームにとっての「十分に良い(good enough)」基準が何かを把握する
- 許容可能なバグの範囲や、完璧でなくてもよい部分など、実質的な基準を一緒に確認する
- 自分の個人的な基準は
- **「10点満点中8点の品質を、期限内に達成する」**こと
- コードは目的をしっかり果たし、致命的な問題はないが、小さな課題は残っていることがある
- 何より重要なのは期限内に提出すること
- ただし、この基準もプロジェクトの文脈に応じて柔軟に調整する
- ときには完璧を追求してスケジュールが遅れてもよく、
- ときには完成度が低くても、まず素早く終わらせるほうが価値がある場合もある
- **「10点満点中8点の品質を、期限内に達成する」**こと
Rough drafts — ラフドラフト、プロトタイピングの実践的な活用と利点
- ソフトウェア開発でも文章を書くのと同じように、**下書き(rough draft, spike, walking skeleton)**を作るのは非常に有用
- できるだけ素早くラフドラフトを実装し、その後で磨き上げて完成形のソリューションへ発展させる
- 私のラフドラフトのコードはバグだらけで、テスト失敗、TODOコメントの乱発、例外未処理、print/logの多用、
パフォーマンス未考慮、WIPコミットメッセージ、不要なパッケージ追加、重複コード、ハードコーディング、リンター警告など、ひどい状態だ - この過程は非効率に見えるかもしれないが、**「少なくとも問題の本質を把握できる状態」**に到達することが目的
- もちろん、このような下書き状態のコードを最終デプロイに出すことはなく、実際のリリース前には必ず整える
(チームから下書きコードをそのまま出そうという圧力がかかることもあるが、できる限り抵抗する) -
ラフドラフト方式の主な利点
- 「未知の問題(unknown unknowns)」を早くあぶり出せる
- 完成後に捨てられるコードよりも、初期のプロトタイプ段階で未知の障害を見つけるほうがはるかに有利
- プロトタイプ作成中に自然に消える問題が多い
- 遅い関数や不適切な構造も、後でそもそも不要になることが多く、時間の無駄を防げる
- 最適化やテストに早すぎる段階で力を注ぎすぎる必要はない
- 集中力を高めてくれる
- 不要なリファクタリング、命名の悩み、別のコードベースの修正などによる気の散りを防ぎ、
いま解くべき問題だけに没頭できる
- 不要なリファクタリング、命名の悩み、別のコードベースの修正などによる気の散りを防ぎ、
- 不要な早期抽象化を防ぐ
- まず動く答えを素早く作る過程では、将来のためだけの不要な抽象化を試みることが少なくなる
- 目の前の問題に集中し、不必要に複雑な設計を避けられる
- 進捗を明確に伝えやすい
- ラフドラフトによって、あとどれくらい残っているかを正確に予測しやすくなる
- まず何かしら動くものを見せることで、ステークホルダーからのフィードバックや方向転換も素早く行える
- 「未知の問題(unknown unknowns)」を早くあぶり出せる
-
ラフドラフトの実践的な運用方法
- 後戻りしにくい決定(binding decision)は、下書き段階で必ず試す
- 例: 言語、フレームワーク、DBスキーマなど、大きな方向性は初期に確認する
- すべての暫定対応/ハックは必ずTODOコメントなどで記録する
- polish(仕上げ)段階で
git grep TODOなどを使って洗い出し、補完する
- polish(仕上げ)段階で
- Top-Down(上位から下位)順で開発する
- UIやAPIなど使われ方からscaffold(骨組み)を書き、内部ロジックはハードコードや仮実装でも許容する
- 実際にはUIやユーザー体験が決まるにつれて下位ロジックが変わることが多いため、上位レイヤーから実装するほうが有利
- 下位から完璧に実装してから上位に合わせるやり方は非効率
- 小さな変更は別パッチに分離する
- ラフドラフトの途中でコードベース改善や依存関係更新の必要性を見つけたら、
その部分だけを別のPR/コミットとして分離して素早く反映する - 全体変更の複雑さを下げ、レビューと統合の速度を高める
- ラフドラフトの途中でコードベース改善や依存関係更新の必要性を見つけたら、
- 後戻りしにくい決定(binding decision)は、下書き段階で必ず試す
参考: 「コードの最初の下書きは捨てよう」、 「今この瞬間に必要な最もシンプルなシステムがベスト」、 「YAGNI(You Aren’t Gonna Need It)」
要件を変えてみる試み
- 少ないほど速く、そして簡単になるという原則を強調する
- 実務では、与えられた課題の要件を緩和できないか常に考える
- 例としての問い:
- 複数の画面を一つにまとめられないか?
- 面倒なエッジケースを本当にすべて扱う必要があるか?
- 1000件の入力対応が必要だとしても、10件だけで十分ではないか?
- 完成版ではなくプロトタイプで代替できないか?
- この機能自体を削ってしまってもよいのではないか?
- 例としての問い:
- このようなアプローチは開発速度と効率を高める
- 組織文化そのものも少しずつ、より遅すぎず合理的なペースへ導くように試みる
- 突発的で大きな変化要求はうまく機能しにくい
- 段階的な提案や議論の仕方の変更などで、少しずつ空気を変えていく
コードにおける気の散り(Distraction)を避ける
- 外部環境(通知、会議)だけでなく、コード作業中に関係のないことへ脱線することも大きな妨げになる
- 私もよく、バグを直しているうちに全然関係ない場所をいじっていて、結局もとの課題が先送りになる
- 具体的な実践法は二つある
- タイマーを設定する: 作業ごとに時間制限を設け、アラームが鳴ったら現在の進み具合を確認する
- 想定より時間がかかっているとき、注意を呼び戻す効果がある
- アラームと同時にgit commitすれば、小さな達成感も得られる
- (この方法は時間見積もりの練習にも効果的)
- ペアプログラミング: 一緒に作業すると無駄な方向へ脱線しにくく、集中力の維持に役立つ
- タイマーを設定する: 作業ごとに時間制限を設け、アラームが鳴ったら現在の進み具合を確認する
- 一部の開発者にはこうした気の散りの回避が自然にできるが、私には意識的な集中と習慣化が必要
小さな単位の変更、小さく分ける
- 以前、大きな単位のパッチや広範囲の変更を奨励する上司がいたが、
実際には非常に非効率だと経験した - 小さく焦点の絞られたdiffのほうが、ほとんどの場合で良いと感じる
- コードを書く負担が少なく、
- コードレビューがより簡単かつ速くなり、同僚の疲労も減り、自分のミスも見つけやすい
- 問題が起きたときにロールバックしやすく、安全
- 一度に変える範囲が小さいため、新しいバグが入り込むリスクも下がる
- 大きな機能や機能追加も、小さな変更の積み重ねで完成する
- 例: 画面追加が必要なら、バグ修正/依存関係アップグレード/機能追加をそれぞれ別パッチに分ける
- 小さな単位での変化が、より速く高品質なソフトウェア開発に役立つと強調する
迅速な開発に本当に役立った具体的なスキル
上で触れた内容はやや抽象的だが、実際に高速開発に効果のある実践的スキルも存在する
-
コードを読む力(Reading code): これまで身につけた最も重要な開発者の能力
- 既存コードを巧みに読み解ければ、デバッグがずっと容易になり、
- オープンソース/サードパーティライブラリのバグや不十分なドキュメントにも、あまりひるまなくなる
- 他人のコードを読んで学べる量も非常に多く、全体的な問題解決能力の向上にも直結する
-
データモデリング(Data modeling): 時間がかかってもデータモデルをきちんと設計することが重要
- 設計の悪いデータベーススキーマは、後でさまざまな問題と高い修正コストを招く
- 無効な状態そのものを表現できないようにする設計は、バグを根本から減らす
- データを保存したり外部とやり取りしたりする場合は、なおさら慎重であるべき
-
スクリプティング(Scripting): BashやPythonなどで短いスクリプトを素早く書く能力は開発効率を最大化する
- 毎週何度も、Markdownの整形、データ整理、重複ファイル探しなどの自動化に活用している
- BashはShellcheckのようなツールで文法ミスを事前に防ぐ
- 堅牢性がそこまで要らない作業なら、LLMの助けを借りて素早く仕上げることもできる
-
デバッガ(Debuggers)の活用: デバッガを使うことは、print/logだけでは不可能な迅速な問題診断とコードフロー把握に不可欠
- 複雑なバグの根本原因を突き止める速度が大きく上がる
-
適切に休むタイミング: 行き詰まったら思い切って休む習慣
- 長時間苦戦しても解けなかった問題が、5分休んだらすぐ解けるという経験は頻繁にある
- 集中の効率という面でも重要
-
純粋関数と不変データ志向: 関数型プログラミングとして、純粋関数とimmutable dataを好むと
- バグが減り、状態追跡の負担が減り、コードの明確さと予測可能性が増す
- 複雑なクラス階層を設計するより、よりシンプルで効果的なことが多い
- 常に可能というわけではないが、基本的にはまずこのやり方を検討する
-
LLM(大規模言語モデル)の活用: **LLM(例: ChatGPTなど)**には欠点もあるが、反復的だったり自動化しやすかったりする開発作業では大きな速度向上をもたらす
- 自分のコードにLLMを取り入れる方法と限界を十分理解したうえで積極的に活用している
- コミュニティのさまざまな経験やヒント、事例も参考にしている
これらすべてのスキルは長い時間をかけて繰り返し練習してきたものであり、実際に迅速な開発における大きな資産になっている
要約
- 私がソフトウェアを素早く開発する中で得た核心的な教訓は次のとおり
- 課題ごとに必要なコード品質基準を明確に把握する
- ラフドラフト(下書き)を素早く作成して全体像をつかむ
- 要件を緩和できる余地を常に探る
- 気の散りに振り回されず集中力を維持する
- 変更は小さく、頻繁にコミットし、大きなパッチを避ける
- 具体的な実践スキル(コードを読む力、データモデリング、デバッグ、スクリプティングなど)を継続的に練習する
- どれもあまりに当たり前に見えるが、実際にこの教訓を得るまでには長い時間がかかった
2件のコメント
共感できる話が多いですね。
コメントも良いですが、こうして誰かが整理して語り、つまり場を設けてくれると、それに対する反論や支持、補足を経て、より完成度が高まるのだと思います。
追記。最近「退屈な技術」という表現をよく見かけますが、英語では boring technology なんですね。
Hacker Newsの意見
この数年で、速く、かつ十分に堅牢なシステムを構築する方法を身につけた
1つのツールを深く習得することが重要だと学んだ。表面的にはより適して見えるツールよりも、自分がよく知っているものの方がはるかに効率的だ。実際、ほとんどのプロジェクトでは Django がちょうどよい選択になる
Django は重すぎないかと心配しながらプロジェクトを始めることもあったが、結局プロジェクトは当初の意図を大きく超えて成長した。たとえばステータスページのアプリを作ったとき、Django の制約を避けようとする努力が非効率だとすぐに気づいた
Django モデルに合うほとんどのアプリでは、データモデルが中核だ。プロトタイプであってもデータモデルのリファクタリングを先送りすると、後でコストと難易度が指数関数的に増大する
ほとんどのアプリにシングルページアプリや重いフロントエンドフレームワークは不要だ。一部には当てはまるかもしれないが、ページ全体の 80% は従来型の Django ビューで十分だ。残りは AlpineJS や HTMX を検討すればよい
たいていの場合、自作する方が簡単だ。Django なら CRM、ステータスページ、サポートシステム、営業プロセスなど、さまざまな機能を素早く作れる。商用 CRM 連携よりずっと速い。
退屈なくらい平凡な技術を選ぶべきだ。Python/Django/Postgres の組み合わせでほとんど解決する。Kubernetes、Redis、RabbitMQ、Celery などは忘れてもいい。例外は Alpine/HTMX で、JS スタックの大半を避けられるからだ
Redis と Kubernetes は、自分にとっては 2025 年の「退屈な技術」だ。どちらも非常に安定していて用途が明確で、欠点もすでによく知られているため信頼しやすい。個人的にこの2つのファンだ。自分が望むことを正確にやってくれるので信頼性が高い
自分も Django が本当に好きだ。ものすごく速くプロジェクトを始めてデプロイできる
本当に「退屈な技術」を選ぶなら、Postgres ですらもう一度考えてみる余地がある
Celery は Django プロジェクトでかなり頻繁に使っている。複雑さは好きではないが、PaaS 環境ではむしろ最も痛みの少ない選択だ
「ほとんどのアプリに SPA や重いフロントエンドフレームワークは必要ない」という主張と、「1つのツールを深く学べ」という助言は衝突しているように見える
ラフドラフトのままコードを残すと、管理者がそのコードをそのまま「最終版」としてリリースしてしまうことがよくある
だから最初から堅牢なコードを書く。テストハーネスですらほぼ本番レベルの堅牢さで作る
核心は、非常に質の高いモジュールを作ることだ。変更される可能性が極めて低い部分や、変更時に非常に大きな問題になる部分は、最初から独立したモジュールとして隔離し、依存関係として import する
こうしたモジュールのおかげで新しいアプリを非常に速く開発でき、品質も継続して高く保てる
自分が実際に使った例としては RVS_Checkbox, ambiamara, RVS_Generic_Swift_Toolbox などがある
質問なのだが、Swift でコメントマーカーとして
* ##################################################################のようなコードパターンを使うのは標準なのだろうかプロジェクトの規模によってアプローチは大きく変わる
個人プロジェクトや小規模チームなら、「速く、荒く」開発するのが最適だ。これが小規模開発の強みだ
小規模ならバグが出てもすぐ直せるし、チーム全員がコード全体をほぼ完全に理解している
規模が大きくなると、アーキテクチャのミスやバグ修正のコストが爆発的に増える。アーキテクチャは必然的に複雑化し、大規模なリファクタリングは事実上不可能になる。こうした環境では、一歩一歩の正確さが最優先であるべきだ
文脈は本当に重要だ。「大規模」がどの程度を意味するかは人によって違うが、自分の経験では、アプリ間 API を早めに合意してフロントエンド/バックエンドの両方がすばやく作業環境を整えられるようにするのが常に正しかった
こういう状況ではシステムを縮小して運用すべきだ。誰もが巨大なシステムを欲しがるが、実際には必要ない
「24時間ゲームジャムではコード品質を気にする必要はない」と言われるが、自分のほとんどのハッカソン/コードレビュー経験では、最も良い成果を出したチームほどコード品質や初歩的なテスト環境もきちんと押さえていた
上の2つの主張(速くやるにはコード品質を捨てるべきだ vs 成績の良いチームほど品質が高い)は、実は矛盾していない。品質の高いチームが必ずしもコードの整然さだけに執着していたわけではない
ゲームジャムの事例では、コードのきれいさにこだわりすぎると、かえって全体の成果物が良くならない。UE blueprint のようなシステムは、なぜ「整然さ」より成果物を優先すべきかを示している
ある人たちはコードの「清潔さ」を全体評価し、別の人たちは不要なコード改善の細かな費用対効果を評価する
「プロトタイピングをすると予想外の unknown unknowns が見えてくる」という話とは逆に、自分が何かを初めて触るときは、いつも長所ばかりが見えて短所はなかなか見えない
実際には、エッジケース処理、ユーザーに親切なエラーメッセージ、副作用の除去など、本当に機能を完成させる段階になって初めて本当の問題(unknown unknowns)が見えてくる
おそらく自分が経験する unknown unknowns はツール/フレームワーク/ライブラリ自体から生じるもので、著者は問題領域そのものにおける unknown unknowns を言っているのだと思う
rough draft があまりに粗すぎてはいけない、という点もその通りだ。雑に済ませてはいけない部分で本当の問題が噴き出す。
自分で使うツールを作るときは雑に作ってもわりと普通に使えるし、そうやって素早く作ったツールは穴だらけでも問題にならない
最近のようにリストラが頻繁なテック業界こそ、ソフトウェア品質とエンジニアの生産性に対する最大の脅威だ
解雇への不安や早い成果への圧力は、創造性と実験精神を殺し、燃え尽きを招く
皆が AI のような流行トピックに群集心理で流され、批判すらできない環境になってしまう
LLM 自動コーディングよりも差し迫った問題だ
ソフトウェア品質に対する最大の脅威は、昔から消費者が品質のためにお金を払わないことだ
プログラミングレベルでのベンダーロックインは、実際には SaaS ロックインよりはるかに破壊的だ
24時間ゲームジャムのような短いサイクルでは、むしろ悪いコードは致命的だと感じる
コードがきれいであるほどミスも減り、ワーキングメモリの負担も減り、終盤で必要な変更や機能追加、問題修正がはるかにやりやすくなる
24時間プロジェクトで最もよく失敗を招くのは、コードを書くのが遅いことではなく、自分で自分を袋小路に追い込んだり、予測不能な問題にぶつかって脱線したりすることだ
もちろん、すべてのバグを潰すべきだと言っているわけではない。しかし基礎的な品質が低いと、プロジェクト全体がつらい体験になる
時間がもっとあるプロジェクトでも、この原則は当てはまる。時間があるからといって雑に書く方がよいわけではない
良いコードを習慣化すれば、追加コストなしで品質を担保できる。そして、たとえ時間が余計にかかっても、結局は価値のあることだ
自分も同感だ。ゲームジャムを何度もやってきたが、「雑なコード」が許されるのは締切前の1〜2時間で、しかも他人が触らないファイルに限る
速く、しかも良いコードを書くには、結局たくさん書いてみるのが答えだ
急いでいるときは fancy な asset loader のようなものは気にせず、ただの静的ファイルで済ませる
「良いコードを書く方が時間がかかる」という認識は誤解だと思う。ある程度以上の要件を満たすなら、良いコードは速度の妨げにならない
「どの程度が good enough なのか」という基準はチームごとに大きく異なり、それが自分のキャリアで最大の対立要因だった
ビッグテック出身者はテスト不足に不満を持ち、スタートアップ出身者はスピードの遅さに不満を持つ
good enough の基準を明確に文書化してチーム内で共有するのは有益だろう
それがまさにチームチャーター、つまり「自分たちの働き方」文書だ
記事で触れられていない重要な要素の1つは、時間経過に伴う開発速度の低下だ
プロジェクトやチームの規模が大きくなると、開発速度は自然に遅くなる
目先の開発速度を多少犠牲にしてでも、長期的な速度低下を抑えるために、早い段階でテスト、ドキュメント化、決定ログ、Agile ミーティングなどを整備する必要がある
observability のような機能や、テストしやすいコード構造を事前に準備しておかないと、後で非常に大きな悪影響が残る
自分はソロ開発者だが、決定ログ・テスト・ドキュメント化の3つの重要性を実感している
自分にもなじみのあるパターンだ。rough draft、あるいはアイデア検証用として、別のスクリプト言語や手動実行を束ねた小さなコードから始める