世界を動かす技術を、日本語で。

ソフトウェアを迅速に構築する方法

概要

  • ソフトウェア開発 では、品質と納期のバランスが重要
  • 完璧さよりも 「十分良い」 コードを重視する姿勢
  • ラフドラフト (荒い試作)を活用し、早期に問題点を発見
  • 要件の見直しや 小さな変更 の積み重ねが効率化に寄与
  • 具体的なスキル や作業習慣で開発速度と品質を向上

ソフトウェア開発における「十分良い」とは

  • 納期品質 の両立が求められるソフトウェア開発現場
  • 速さを優先しすぎると バグ保守性の低下 を招く
  • 完璧を目指しすぎると リリース遅延 や進捗停滞
  • プロジェクトやチームごとに「どこまで良くすべきか」の 基準 が異なる現実
  • 8割の完成度 を期限内に出すことを個人ルールとする実践
    • ただし、用途やプロジェクトによって 柔軟な対応 も必要

ラフドラフト(荒い試作)活用術

  • 「スパイク」「ウォーキングスケルトン」 と呼ばれる手法の活用
  • まずは 動くが粗いコード を素早く作成し、後から洗練
  • ラフドラフトの特徴
    • バグ未対応ケースTODOコメント が多い
    • print文ハードコーディングパフォーマンス無視
    • WIP (Work In Progress)などの暫定コミット
  • この段階で 未知の課題不要な機能 を早期発見
  • 最終版に仕上げる前に 問題点を洗い出し修正

ラフドラフト作成時の具体的工夫

  • 変更しにくい設計要素 (言語選定やDB設計)を早めに検証
  • 手抜き箇所には TODO を明記し、後で一括修正
  • トップダウン (UIやAPIの理想像から作り始める)アプローチ
  • ラフドラフト中に発見した改善点は 個別パッチ で先に対応
  • 参考資料:「Throw away your first draft of your code」「Best Simple System for Now」「YAGNI」など

要件の見直し・緩和

  • やらないことを増やす ことで開発効率化
    • 画面を 統合 して数を減らす
    • 難しい エッジケース の対応を見送る
    • API入力数 を制限
    • プロトタイプ で済ませる提案
    • 実施自体を見直す (不要ならやらない)
  • 組織文化の変革も視野に、 小さな提案 から始める工夫

コードベースで迷子にならない工夫

  • タイマー をセットし、作業の脱線を防止
  • ペアプログラミング で集中力維持
  • 自己管理や 意識的な行動 で生産性向上

小さな変更を積み重ねる

  • 大規模パッチ は管理・レビュー・ロールバックが難しい
  • 小さく焦点を絞った差分 の利点
    • 書きやすく、 頭の負担 が小さい
    • レビューしやすい、マージも早い
    • バグのリスク低減、ロールバックも容易
  • 例:新画面追加前に バグ修正依存関係アップグレード を個別パッチで対応

開発を速くするための具体的スキル

  • コードリーディング 力:デバッグや学習、外部依存の理解に必須
  • データモデリング :不正な状態を防ぎ、後々のトラブル回避
  • スクリプト作成 :BashやPythonでの自動化・補助ツール活用
    • Bashには Shellcheck 推奨
  • デバッガ の活用:print文より効率的な問題解決
  • 適切な休憩 :行き詰まり時のリフレッシュで効率回復
  • 純粋関数イミュータブルデータ 推奨:バグ減少と理解容易化
  • LLM (大規模言語モデル)の活用:得意分野を見極めて日常利用

まとめ

  • 求められる品質レベル を見極める
  • ラフドラフト から始めて早期に課題を発見
  • 要件緩和 を積極的に提案
  • 小さな変更 を重ねて品質とスピードを両立
  • 個別スキル作業習慣 の継続的な向上

必要に応じて、次の話題や具体的なテクニックに関するセクションを追加できます。

Hackerたちの意見

記事であまり触れられていない重要なポイントは、時間とともに開発スピードがどう変わるかってことだね。プロジェクトやチームの規模が大きくなると、時間が経つにつれてそのスピードは落ちていく。減少率を最小限に抑えるためには、長期的な速度のために短期的な開発を遅らせるようなことをしなきゃいけないかもしれない。例えば、テストやドキュメント、意思決定ログ、アジャイルのセレモニーなんかがそうだね。初期開発でのいくつかの見落としは、後々長い間ネガティブな影響を及ぼすことがある。例えば、最初からコードに観測可能性を組み込まなかったり、テストしやすさを明確な目標にしてコードを構造化しなかったりすることが挙げられる。

ソロ開発者として、僕は意思決定ログ、テスト、ドキュメントの順で重要だって言えるよ。個人的には「決定ログ」じゃなくて「ラボノート」を使ってるんだけど、リアルタイムでデザインを記録していて、それがテストやドキュメントの基礎になるんだ。ラボノートがあると、たとえ遅れて始めても、より良いドキュメントを早く書けるし、テストによってデザインが時間とともにずれないか確認できる。週末に書く一回限りのツールなら盲目的に始めるのもアリだけど、長く使うものなら、ゆっくりとした基盤を作ることで、その上に築くものがしっかりして、問題に対して合理的で、何より理解しやすくてメンテナンスしやすいものになるよ。あと、あまり人気のない意見だけど、まず紙でデザインしてからデジタル化するのがいいと思う。

「未知の未知」を明らかにすることができる。プロトタイプは、予想できなかったことを明らかにすることが多い。これは僕の経験とは真逆だな。何かをいじっているときは、良い部分だけを体験していて、悪い部分は全く感じない…いわばハネムーン期みたいなもんだ。エッジケースをカバーしたり、無効な状態を防いだり、ユーザーに役立つエラーメッセージを表示したり、潜在的な副作用を排除しようとするまで、「未知の未知」に気づくことはない。

あなたが言ってるのは、ツールやフレームワーク、ライブラリにおける未知の未知のことだと思う。著者は問題空間における未知の未知について話しているんじゃないかな。

そうだね、自分だけが使うツールを作るのはすごくスムーズだよね。穴だらけで、全体的に揺れるカードハウスみたいでも、うまく使えるから。

そうだね。ラフドラフトがあまりにもラフすぎることについて警告したかったんだ。実際の問題があるところは、手を抜いてはいけない部分だと思う。ラリーのパイロットは、持続的なペースで偵察をするんじゃないかな。そうしないと、例えばコーナーの前のバンプが厄介だって気づかないかもしれない。

スケールがここではかなり重要だと思う。自分で何かを作ったり、小さなチームでやっているなら、投稿に書いてあることには完全に同意するよ。実際、そういう状況では迅速で雑な開発手法に頼るべきだと思う。これが小規模開発の強みだからね。うまくやれば、大きなプロジェクトよりもずっと早く進められる。バグは小さなチームやソロ開発者にとってはほとんどいつでも簡単に修正できるし、関わる全員がプロジェクト全体のほぼ完璧なメンタルモデルを持っているから、コード自体もコンウェイの法則のおかげで比較的シンプルに保たれる。大規模な開発プロジェクトでは、バグや特にアーキテクチャのミスを修正するのは、コードの理解が断片的で、アーキテクチャが悪夢のように複雑だから、指数関数的にコストがかかる。大規模なリファクタリングは、コードベースの一部をロックダウンすることを意味して、数十人から数百人が何もできなくなる(つまり、ほとんど起こらない)。そんな状況では、全てのステップでの正確さが重要な焦点になるべきだと思う。スケールの経済は、個々の開発者がそんな風に働いていても、許容できるペースで物事を進める助けになるよ。

うーん、コンテキストがすごく大事だね。君が考える大規模な開発プロジェクトが何を指しているのか分からないけど、私が思っているよりももっと大きいかもしれない。でも、アプリ間のAPIを早めに立ち上げて、データベースチームからフロントエンドチームやアプリチームに、何らかのバックエンド/APIチームを通じて作業環境を整えるのが正しい選択だったといつも思ってる。できるだけ早くプロダクションサーバーに載せて、DNSだけが欠けている状態にするのが、テストやチーム間のバグや問題を浮き彫りにするのにすごく役立つよ。著者は主にコードの観点から話してるけど、個人的には大きなチームではもっと重要だと思う。(余談だけど、チーム間で依存関係の層を作るようなアーキテクチャは、私の視点では悪いアイデアだと思うけど、まだよく行われているね。)

ここがスケールダウンするポイントだね。誰もこんな巨大なシステムは必要ないのに、なぜかみんな欲しがるんだよね…

最近、十分に堅牢なシステムを素早く構築する方法を学んだよ。ここで学んだことをいくつか挙げるね。* 一つのツールをしっかりと使いこなすこと。表面的には問題に適しているように見えるツールよりも、自分がよく知っているツールを使う方が多くの場合良い。非常に多くの現実の問題に対して、Djangoは絶妙な選択なんだ。何度かプロジェクトを始めるときにDjangoは重すぎるかもと思ったけど、すぐにそのプロジェクトは初期のアイデアを超えて成長した。例えば、最近ステータスページアプリを作ったんだけど、最初は単一ファイルのDjangoアプリだったけど、Djangoの制限を回避するのは意味がないとすぐに気づいた。* Djangoモデルに合うアプリケーションでは、データモデルが全ての中心にある。ラフなプロトタイプを作るときでも、データモデルのリファクタリングを後回しにしないこと。時間が経つにつれて、変更するのがどんどん高くつくし難しくなるから。* ほとんどのアプリケーションはシングルページアプリである必要もないし、重いフロントエンドフレームワークも必要ない。恩恵を受けられるものでも、従来のDjangoビューで80%のページは十分だよ。残りについてはAlpineJS/HTMXを考えてみて。* ほとんどの場合、自分で作った方が簡単だよ。顧客を保存して編集する必要がある?Djangoを使えば、数時間でアプリ内にシンプルなCRMアプリを開発できる。商業CRMを統合するのはもっと時間がかかる。これはステータスページ、CRM、サポートシステム、営業プロセスなど、ほとんどのDjangoアプリやライブラリにも当てはまるよ。* いつも超つまらない技術を選ぶこと。全てにPython/Django/Postgresを使って。KubernetesやRedis、RabbitMQ、Celeryは忘れて。Alpine/HTMXは例外だけど、JavaScriptスタックの多くを避けられるからね。

僕もDjangoが大好きだよ。すぐに動くプロジェクトを簡単に立ち上げられるからね。普段の仕事ではGoを使ってるけど、まあそれも悪くはないけど、シンプルなAPIエンドポイントを書くのに10倍のコードを書く羽目になるし、フィルタリングやページネーションのためにクエリパラメータを追加すると、さらに長くなる。権限モデルを追加するのも同じような感じだよ。もちろんパフォーマンスの違いは大きいけど、ほとんどのことに関してはDBクエリがパフォーマンスを支配してるから、Pythonでもそうだね。

完全に同意だね。自己完結型のSaaSスタートアップには、Djangoを(ほぼ)何にでも使うのが簡単だと思う。マーケティングはWagtailでできるし、サポートは再利用可能なアプリで管理してる。これは、各ページにあるシンプルな静的要素(Intercomに似てる)で、標準のDjangoページにリダイレクトして、問題についての情報を集めるんだ(認証されてるユーザーなら、そのユーザー情報も含めて)。さらにスタックを簡素化するために、バックアップにはSQLiteとBorgを使ってる。キャッシングはDiskcacheを利用してる。デプロイは少し複雑だけど、コンテナとpodmanをsystemdで使ってるけど、git pull & gunicorn restartでも簡単にできるよ。フロントエンドのやり方は何度か変わったけど、Alpine & HTMXは自分には制約が多すぎて、代わりにTypescriptをdjango-viteと組み合わせて使うのが好きだね。フロントエンドのツールを使うことになるけど、TailwindCSSやReact、Typescriptなんかも使えるからね。

Djangoは、どのフレームワークよりもROIが高い気がする。

いつも超つまらない技術を選んでね。全部Python/Django/Postgresを使えばいいんだ。いや、Postgresを考える前に二度考えてみて。Sqliteは、特にローカル開発や孤立したCIインスタンスを立ち上げるときには、意外とスケールするからね。小さなアプリには、実際にプロダクションでも十分な性能を発揮することが多いよ。

KubernetesやRedisは忘れよう。君に同意するけど、これら二つは2025年のつまらない技術だと思ってる。すごく信頼性が高いし、使うべきケースがはっきりしていて、どこで使うべきでないかもよく分かってる。興味が少しずつ薄れてきてる気もする。個人的には、すごく安定してるから大好きなんだ。

ほとんどのことに同意するけど、Celeryは私のDjangoプロジェクトでは結構一般的だよ。複雑さは嫌だけど、特にホスティングにPaaSを使うときは、たいてい一番楽な選択肢なんだ。毎回「今回はなしでやってみよう」と思うけど、HTTPコールでトリガーされるジョブがタイムアウトになっちゃうんだよね。その時はスレッドかcronジョブ(PaaSだと難しい)かCeleryのどれかになる。君はどうしてる?

Djangoでコンポーネントをうまく作って、ユーザーインタラクションをどう扱うか教えて?

ほとんどのアプリケーションはシングルページアプリである必要も、重いフロントエンドフレームワークを必要ともしない。恩恵を受けられるものでも、従来のDjangoのビューで80%のページは十分だよ。残りの部分については、AlpineJSやHTMXを考えてみて。これって「一つのツールをしっかり学ぶ」っていうのと矛盾しない?私はすべてのウェブページをReactで書いてるけど、それはすべてがSPAである必要があると思ってるわけじゃなくて、クライアントサイドの状態を管理する必要があるものが多いからなんだ。その時点で、最初は重すぎると思っても、すべてをReactでやる方が簡単なんだよね。

Celeryは忘れて。Celeryなしでバックグラウンドジョブ処理をどうやるの?私が開発したアプリやウェブサイトは、どれも何らかのバックグラウンドジョブ処理が必要だった。PythonではCeleryを使って、RailsではSidekiq、Node.jsではFaktoryを使ってる。最大の欠点の一つは(少し前までは)、これを設定するのが自分のアプリへのハッキーなWebhookコールをしなきゃいけなかったことだよ。最大N秒のリクエスト/レスポンスが必要だった。

これは素晴らしいけど、非常に限られたプログラミングの問題に対してのものだね。あなたは明らかに中規模のウェブアプリケーションに取り組んでいる。例えば、KubernetesやRedisは、バックエンドサービスがある規模に達するとほぼ必須になることがあるよ。

実は、プロトタイプでも最初から「ちゃんと」作るようにしてるんだ。成功してるとは言えないけど、少なくとも頑張ってるよ。これがすべてにテストを書くって意味じゃないし、時にはテストを書かないこともあるけど、コードを「テストしやすく」するように最善を尽くしてる。これに時間がかかるべきじゃないんだよね。テストしやすくするためにクラスを増やすのは、もう間違ってるってこと。可読性を妥協するわけでもないし、「クリーンコード」みたいなやり方は避けるべきだと思う。関数は必要な大きさにするべきだし、特にRubyやJavaを使ってる人たちがここに時間をかけすぎることが多い気がする。個人的には、5行の関数がたくさんあるのは全然必要ないから、このステップは飛ばしちゃう。抽象化について妥協するわけでもないし、「三つの法則」も好きじゃないな。後で余計な手間が増えるからね。でも、ジョン・アウスターハウトのスタイルで、DEEPなクラスとSMALLなインターフェースを好むから、コードを書くのにそんなに時間はかからないよ。考える必要はあるけど、普通のことだし。人が inertia でやらないだけのことだと思う。一つだけこだわってるのはスコープかな。スコープが大きすぎると、プロトタイプやMVPには向かないから、縮小するために戦うよ。EDIT: kukkeliskuuが「一つのツールをしっかり学ぶ」って言ってたけど、これも重要だね。プロトタイプや初期段階で「流れに逆らわない」ようにしよう。フレームワークと戦ってるなら、間違った道に進んでるよ。

個人的には、最初からちゃんとやろうとすると逆に時間がかかって、全体的にデザインが悪くなることが多いんだ。でも、2回目のパスではハックを修正したり、書き直すべきものをしっかりやるようにしてる。最初にうまくやろうとすることに対して2つの問題があるんだ: - 実際に実装しないと要件の細かいところが分からないから、完璧じゃない知識でアーキテクチャを設計することになる - 分析麻痺に陥りやすい FWIW、ジョン・アウスターハウトの大ファンなんだ。ソフトウェアデザインに関する本の中で、これが一番好きかもしれない。

「ラフドラフト」を「ドラフト」として維持するのが難しいって感じる。典型的な技術マネージャーがいると、最終的なコードになっちゃうんだ。最初から出荷用のコードを書く傾向があるけど、柔軟性を持たせるようにしてる。どこでも「出荷する」ことを学んだし、テストハーネスもかなり堅牢な出荷品質のアプリになることが多い。大事なのは、非常に高品質なモジュールだね。変わらないことが分かっている部分は常にあるし、もし変わる場合はそれが大きな問題になるから、そういう部分はスタンドアロンのモジュールに分けて、依存関係としてインポートしてる。これが最近リニューアルしたモジュールの一例だよ[0]。このアプリ[1]の設定ポップオーバーで使ってるし、ほとんどすべてにインポートしてるベースライン依存関係もある[2]。これで新しいアプリを開発するのがすごく早くなって、品質もかなり高く保てるんだ。たとえそれが主な目的じゃなくてもね。[0] https://github.com/RiftValleySoftware/RVS_Checkbox [1] https://github.com/RiftValleySoftware/ambiamara [2] https://github.com/RiftValleySoftware/RVS_Generic_Swift_Tool...

ちょっと脱線するけど、Swiftで「* ################################################################## / コメントマーカー」があるのは普通なのかな?ソースコードの中で視覚的にかなり目立つようになっちゃうよね。> / ###################################################################################################################################### / // MARK: - PUBLIC BASE CLASS OVERRIDES - / ###################################################################################################################################### */

すごく共感する。ラフドラフト、手動実行が多くて、ユニットテストの実行者にラップされてたり、アイデアを確認するために別のスクリプト言語で書かれたりすることがよくある。これが、実際にはそのものを作りたくない理由を示すのに役立ったことが多い。人々が望むようには機能しないからね。コードの中の気が散る部分についても本当に実感する。自分は「整理整頓したくなる」傾向があって、そうするとウサギの穴にハマって、変更が大きくなって仲間がレビューするのが嫌がるサイズになっちゃう。こういう試みは、完全に捨てて元に戻ることが多いし、メインのことを小さく集中させるために頻繁に小さなローカルコミットが助けになる。時には何かを救って、時間があれば別のPRで公開することもある。ビジネスは結果を早く求めていて、コードのトレードオフを理解しないことが多い。負債が山のような大きさになるまで、些細な変更すらも痛みを伴うからね。でも、バランスが大事で、プロジェクトによって違うかもしれない。小さく、集中した、シンプルな変更は確実に助けになる。ただ、人々は大きな解決策を小さな部分に分けるのが得意じゃないことが多い。完全に使われないコードを含むコミットを見かけることがあって、「これは将来の作業の一部になる」ってコメントがついてることがあるけど、優先順位が変わったり、人が入れ替わったりして、1年後にはそれが現在の状態に適用されなくなって、誰もその計画を覚えていないことが多い。

例えば、24時間のゲームジャムでゲームを作るとき、クリーンなコードを優先するのはあまり良くないと思うよ。時間の無駄だし!コードがエレガントでバグがないかなんて、誰が気にするの?ちょっと話が逸れるけど、過去に(ゲーム以外の)ハッカソンでコードレビューをたくさんやってきた経験から言うと、パフォーマンスが良かったチームは、だいたいコードの質も良くて、基本的なテスト環境も整ってたことが多かったよ。

ゲームのユースケースがこのアドバイスを適切にしてるね。24時間でゲームを作るのに、ソースコードのクリーンさに1時間以上も悩んでたら、うまくいかないと思うよ。UEブループリントみたいなシステムは、クリーンさを追求することがどれだけ無意味かを、最終的な製品体験と比べるとよく分かるよね。

この二つの発言は矛盾してないよ。チームが最高のコード品質を持っているからといって、クリーンなコードを優先しているわけではないからね。

コードのクリーンさを全体的に見る人もいれば(このコードベースは汚い vs. エレガントでバグがない)、非機能的なコード改善のコスト/ベネフィットを細かく分析する人もいると思う。後者の方が、どんな状況でも前者を大きく上回ると確信してるよ。ハッカソンの短期間でのクイックな作業でも、超堅牢なプロダクションコードでもね。

「私のチームにとっての『十分良い』とは何か?」受け入れられるバグは何か?早く物事を進めるために、どこで完璧じゃない仕事をしてもいいのか?私はプロジェクトを頻繁に行き来していて、これがキャリアの中でチーム間の対立の最大の原因だった。異なるバックグラウンドを持つ人たちが「十分良い」と思うレベルはそれぞれ違う。大手テック出身の人は、他の誰も徹底的にテストしていないことにイライラしてるし、スタートアップ出身の人は、みんなが遅すぎることにフラストレーションを感じてる。チームが同じページにいるために、「十分良い」を明確にできたらいいのに。

これはチームチャーターだね。「私たちの働き方」ドキュメント。 https://asana.com/resources/team-charter-template

例えば、24時間のゲームジャムのためにゲームを作っているなら、クリーンなコードを優先するのはあまり良くないよね。それは時間の無駄だ!コードがエレガントでバグがないなんて、誰が本当に気にするの?24時間のゲームジャムに参加したことがあるけど、逆のことを感じたよ。本当に急いでいるときこそ、悪いコードは許されない。より良いコードを書くことで、正しく作るのが楽になって、作業メモリへの負担も減るし、早く試すことができて、最後の変更もやりやすくなる。最後の方で機能を追加するのも簡単になるし、重要なのは、集中的なデバッグが必要になる可能性を減らして、デバッグ自体も楽にしてくれること。良いコードで作業するのは本当に楽だよ。24時間のプロジェクトを壊すのは、コードを書くのが遅いことじゃなくて、自分を追い込むようなコードを書いたり、作業を脱線させる問題にぶつかることだよ。それは解決するのに何時間もかかるし、締切を過ぎても解決しないこともある。ゲームジャムではすべてのバグを潰そうとするのは無理だけど、それはやっていることの問題であって、やり方の問題じゃない。その状況でも良いコードを書きたいのは、時間の制約の中で自分の生活を楽にするためだし、いくらかのバグには目をつぶれるとしても、ゲームを不快にしたりプレイできなくするバグはたくさんあるから。どんなバグでも修正しなきゃいけないし、できれば少ない方がいいし、簡単なバグを修正したい!長期的な視点でも同じことが言えるよ。時間があれば、悪いコードに対処する余裕があるけど、それが書く意味があるってことにはならない!もちろん、正しい習慣を身につければ、質の高いコードを書くのはほぼタダになってくるけど、たとえ本当に意味がある遅れをもたらしたとしても、期待値的にはやる価値があると思うよ。

同意だね。いいコードを書くのに時間がかかるっていうのは、誤解だと思う。少なくとも、ざっくりとでも要件を満たしたいならね。

まあ、派手なアセットローダーは無視して、代わりにファイルを静的にインクルードするかもしれないね。それか、パスを処理する必要があって、最速の解決策が幅優先探索だったり。これが「悪いコード」ってわけじゃないけど、実装が早くて、たくさんの計算力で解決できる悪い解決策かもしれない。もちろん、そういう機能を提供する使い回しのモジュールを使うこともできるけど、競技のルールで禁止されてるかもしれないし、わからないな…。