この記事の目次
Step3: 実装する
実装計画ができたら、いよいよコードを書いていきます。ここでは、初心者が陥りがちな失敗を避け、確実に動くコードを書くための方法を解説します。
なぜ「実装の進め方」が重要なのか
初心者あるある: 「とりあえず全部書いてから動かそう」→ エラーが大量に出て、どこが間違っているか分からない
実装の進め方を間違えると、以下のような問題が起こります:
- エラーの原因特定が困難: 100行書いてから動かすと、どの行が原因か分からない
- 手戻りが大きい: 根本的な設計ミスに最後に気づくと、全部書き直し
- デバッグ時間の増大: 複雑に絡み合ったバグは解決が難しい
正しい進め方を身につけると:
- エラーが出ても原因がすぐ分かる
- 小さな成功を積み重ねてモチベーションを保てる
- 確実に動くコードが書ける
原則1: 小さく作って、小さく確認
なぜ小さく作るのか
❌ 悪い例:一気に全部書く
┌─────────────────────────────────────┐
│ 100行のコードを書く │
│ ↓ │
│ 実行してみる │
│ ↓ │
│ エラーが10個出る │
│ ↓ │
│ どこが原因か分からない... │
└─────────────────────────────────────┘
✅ 良い例:小さく書いて確認を繰り返す
┌─────────────────────────────────────┐
│ 10行書く → 動作確認 → OK! │
│ ↓ │
│ 次の10行書く → 動作確認 → OK! │
│ ↓ │
│ さらに10行書く → エラー! │
│ ↓ │
│ 直前の10行が原因と分かる → すぐ修正 │
└─────────────────────────────────────┘
具体的な進め方
1. 骨格を先に作る
まず、処理の流れ(骨格)だけを作ります。
// 悪い例:いきなり詳細を書き始める
public void processOrder(Order order) {
// 在庫チェックのロジックをいきなり100行...
}
// 良い例:まず骨格を作る
public void processOrder(Order order) {
// Step1: 入力値チェック
validateOrder(order);
// Step2: 在庫確認
checkStock(order);
// Step3: 注文登録
registerOrder(order);
// Step4: 通知送信
sendNotification(order);
}
// 各メソッドは最初は空でOK
private void validateOrder(Order order) {
// TODO: あとで実装
}
2. 一つずつ肉付けしていく
骨格ができたら、一つずつ中身を実装します。
// Step1から順番に実装
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("注文がnullです");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("注文商品がありません");
}
// ここで一度動作確認!
}
3. 一つ実装したら必ず動作確認
実装 → 保存 → 実行 → 確認 → 次へ
このサイクルを短く回すことが大切です。
原則2: 動くコードを維持する
なぜ「常に動く状態」を保つのか
初心者あるある: 「あとでまとめて直すから、今はコンパイルエラーがあってもいいや」
これは危険な考え方です:
- エラーが雪だるま式に増える: 1つのエラーを放置すると、それに依存する部分もエラーになる
- 何が正しい状態か分からなくなる: エラーだらけだと、どこまでが動いていたか不明
- 心理的負担が増大: エラーの山を見るとやる気がなくなる
実践方法
1. コンパイルエラーは即座に直す
コンパイルエラーが出たら、次の行を書く前に直しましょう。
// コンパイルエラーを放置して次に進む ❌
public void method1() {
unknownMethod(); // エラー!でも後で直そう...
}
public void method2() {
// method1のエラーを直さないまま次を書く
// → エラーが増えていく
}
2. TODOコメントで仮実装
まだ実装しない部分は、TODOコメントと仮の実装で動く状態を保ちます。
private int calculateDiscount(Order order) {
// TODO: 割引計算ロジックを実装
return 0; // 仮の値を返して動く状態を維持
}
3. 定期的にコミット
動く状態になったら、こまめにコミットしましょう。
# 機能の一部が動いたらコミット
git add .
git commit -m "注文バリデーション機能を追加"
これにより、いつでも「動いていた状態」に戻れます。
原則3: 既存コードに合わせる
なぜ既存コードに合わせるのか
初心者あるある: 「自分の好きな書き方のほうが読みやすいから」
チーム開発では、個人の好みより一貫性が重要です:
- 読みやすさ: 統一されたコードは誰が見ても理解しやすい
- 保守性: スタイルが混在すると、どれが正しいか分からなくなる
- レビュー効率: 「なぜこの書き方?」という指摘を避けられる
確認すべきポイント
1. 命名規則
// 既存コードがキャメルケースなら
private int orderCount; // ✅ 既存に合わせる
private int order_count; // ❌ スネークケースは使わない
// 既存コードがget/setを使っているなら
public int getOrderCount() // ✅ 既存に合わせる
public int orderCount() // ❌ 独自スタイルは避ける
2. インデントとフォーマット
// 既存コードがスペース4つなら
if (condition) {
doSomething(); // ✅ スペース4つ
}
// タブや2スペースは使わない
if (condition) {
doSomething(); // ❌ スペース2つ
}
3. エラーハンドリング
// 既存コードが例外をスローしているなら
if (order == null) {
throw new IllegalArgumentException("..."); // ✅
}
// 独自の方法を使わない
if (order == null) {
return null; // ❌ 既存と異なる方法
}
4. ログ出力
// 既存コードのログレベルに合わせる
logger.info("注文処理開始: orderId={}", orderId); // ✅
// 独自のフォーマットを使わない
System.out.println("注文処理開始: " + orderId); // ❌
既存コードの調べ方
- 同じような機能を探す: 「注文処理」を実装するなら、既存の「商品処理」を参考に
- コーディング規約を確認: プロジェクトにあれば必ず読む
- 先輩に聞く: 「この書き方で合っていますか?」と確認
原則4: YAGNIの原則を守る
YAGNIとは
YAGNI = "You Aren't Gonna Need It"(それは必要にならない)
初心者あるある: 「将来こういう機能が必要になるかも」→ 今は使わない機能を作り込む
なぜYAGNIが重要か
❌ 過剰な実装の問題
┌─────────────────────────────────────┐
│ 今必要な機能: 1つ │
│ 「将来のため」に作った機能: 5つ │
│ ↓ │
│ ・開発時間が5倍 │
│ ・将来の仕様変更で作り直し │
│ ・使われないコードが負債になる │
└─────────────────────────────────────┘
現実:
- 「将来必要になる」と思った機能の多くは、結局使われない
- 使われるとしても、想像と違う形で必要になる
- 今作っても、その時には仕様が変わっている
具体例
// ❌ 過剰な実装
public class OrderProcessor {
// 今は日本円だけだが、将来のために多通貨対応...
private Map<String, CurrencyConverter> converters;
// 今は1店舗だが、将来のためにマルチテナント対応...
private TenantManager tenantManager;
// 今は同期処理だが、将来のために非同期対応...
private AsyncExecutor asyncExecutor;
// 実際に必要な処理はこれだけ
public void process(Order order) {
save(order);
}
}
// ✅ 必要最小限の実装
public class OrderProcessor {
public void process(Order order) {
save(order);
}
}
YAGNIの実践
- 仕様書に書かれていることだけ実装する
- 「あると便利」は作らない
- 迷ったら作らない
- 本当に必要になったら、その時に作る
原則5: 読みやすさを優先する
なぜ読みやすさが重要か
事実: コードは書く時間より読む時間のほうが圧倒的に長い
- 自分で書いたコードも、1週間後には「他人のコード」
- チームメンバーがあなたのコードを読む
- 将来の保守担当者が読む
読みやすいコードの書き方
1. 意味のある名前をつける
// ❌ 分かりにくい名前
int d; // 何の日数?
String s; // 何の文字列?
List<Order> list; // 何のリスト?
// ✅ 意味が分かる名前
int daysSinceLastOrder;
String customerName;
List<Order> pendingOrders;
2. 1つのメソッドは1つのことだけ
// ❌ 1つのメソッドで複数のことをやる
public void processOrder(Order order) {
// バリデーション(30行)
// 在庫チェック(20行)
// 注文登録(25行)
// メール送信(15行)
// ログ出力(10行)
// 合計100行...
}
// ✅ 役割ごとにメソッドを分ける
public void processOrder(Order order) {
validateOrder(order);
checkStock(order);
registerOrder(order);
sendConfirmationEmail(order);
}
3. ネストを浅くする
// ❌ 深いネスト
public void process(Order order) {
if (order != null) {
if (order.getItems() != null) {
if (!order.getItems().isEmpty()) {
for (Item item : order.getItems()) {
if (item.getStock() > 0) {
// 処理...
}
}
}
}
}
}
// ✅ 早期リターンでネストを減らす
public void process(Order order) {
if (order == null) return;
if (order.getItems() == null) return;
if (order.getItems().isEmpty()) return;
for (Item item : order.getItems()) {
if (item.getStock() <= 0) continue;
// 処理...
}
}
4. マジックナンバーを避ける
// ❌ 意味不明な数値
if (status == 3) {
discount = price * 0.1;
}
// ✅ 定数で意味を明確に
private static final int STATUS_PREMIUM = 3;
private static final double PREMIUM_DISCOUNT_RATE = 0.1;
if (status == STATUS_PREMIUM) {
discount = price * PREMIUM_DISCOUNT_RATE;
}
実践チェックリスト
実装中は、以下を意識しましょう:
進め方
- [ ] 10〜20行書いたら動作確認している
- [ ] コンパイルエラーを放置していない
- [ ] 動く状態を常に維持している
コードの品質
- [ ] 既存コードのスタイルに合わせている
- [ ] 仕様書にないことは実装していない(YAGNI)
- [ ] 意味のある名前をつけている
- [ ] 1メソッドが長すぎない(目安: 30行以内)
確認
- [ ] 定期的にコミットしている
- [ ] 動作確認してから次に進んでいる
次のステップ
実装が一段落したら、次は動作確認です。