← 戻る

アンチパターン警告集

"私たちは正しいコードを書くために、間違ったコードを知らねばならない"

"GOTOは有害である。プログラムの質は GOTO 文の数に反比例する。"— Edsger Dijkstra(1968年の論文)

普遍の悪習 — どの言語でも避ける

言語に依存しないが、毎日どこかで誰かがやらかしている10種

1. マジックナンバー

数字の意味が読み手に伝わらず、変更時に影響範囲不明

if (age > 65) applyDiscount();
setTimeout(fn, 86400000);
const SENIOR_AGE = 65;
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
if (age > SENIOR_AGE) applyDiscount();
setTimeout(fn, ONE_DAY_MS);

2. 神クラス(God Class)

1つのクラスが何でも知っている=1つの変更で全体が壊れる

UserManager に認証・課金・メール送信・履歴管理が全部あったら危険信号。各責務を別クラスに分けよ。

3. コピペプログラミング

同じバグを複数箇所に量産。修正漏れの温床

3回コピーしたら関数化/共通化を考える(Rule of Three)

4. 早すぎる最適化

読みにくいコードを生み、しかも本当のボトルネックは別の場所にある

Knuthの言葉:「早すぎる最適化は諸悪の根源」。まず動かす→計測→ボトルネックだけ最適化。

5. 例外を握りつぶす 致命

エラーが消え、原因不明の障害になる。デバッグ地獄。

try {
  doSomething();
} catch (e) {
  // 何もしない
}
try {
  doSomething();
} catch (e) {
  logger.error("doSomething失敗", { cause: e });
  throw new AppError("処理失敗", { cause: e });
}

6. 名前の手抜き

data, info, manager, util などは何も伝えない。コメントで補おうとする時点で負け

function process(data) { ... }
function calculateMonthlyRevenue(orders) { ... }

7. ネスト過多(地獄のピラミッド)

if (user) {
  if (user.isActive) {
    if (user.hasPermission) {
      if (user.balance > 0) {
        // 本処理
      }
    }
  }
}
if (!user) return;
if (!user.isActive) return;
if (!user.hasPermission) return;
if (user.balance <= 0) return;
// 本処理(早期リターンで平坦化)

8. テストなし

変更が怖くなり、コードが腐る。直したつもりが別を壊す。

最低でも公開関数にユニットテスト。E2Eで主要フローをカバー。

9. ログ不在

障害発生時に何が起きたか分からない

構造化ログ(JSON)で「誰が・いつ・何をしようとして・どう失敗したか」を残す

10. ハードコード(密結合)

環境差・将来変更に弱い

API キー・URL・DB 接続先 → 環境変数や設定ファイルに分離

"プログラムが書かれてから読まれるまでの間に、コードを理解する人は、あなた自身を含めて多い。読みやすさを優先せよ。"— Robert C. Martin『Clean Code』

JavaScript / TypeScript の罠

== の誘惑

if (x == null) ...
0 == ""        // true
"0" == false   // true
if (x === null || x === undefined) ...
// または ?? を活用

var の使用

ホイスティングの混乱、ブロックスコープなし

const をデフォルト、再代入する時のみ let。var は使わない。

async関数で await し忘れ

async function save() {
  db.write(data);  // awaitなし → 失敗してもUnhandledPromiseRejection
}
async function save() {
  await db.write(data);
}

any の濫用

TypeScriptを使う意味を放棄している

不明なら unknown、設計が決まらないなら型定義を考え直す

npm install --force 致命

依存の整合性を壊す。次の人が泣く

依存衝突は根本解決を。peer依存は明示的に管理。

Python の罠

ミュータブルなデフォルト引数 致命

def add_item(item, items=[]):  # ← 共有される!
    items.append(item)
    return items

add_item(1)  # [1]
add_item(2)  # [1, 2] ← 想定外
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

except: で全例外捕捉

try:
    do()
except:  # SystemExit, KeyboardInterruptまで捕まえる
    pass
try:
    do()
except SpecificError as e:
    logger.exception(e)
    raise

仮想環境を使わない

venv / poetry / uv を必ず使う。グローバルに pip install は次のプロジェクトを壊す

== でNoneと比較

if x == None:
if x is None:

SQL の罠

SELECT * の使用

列追加でコードが壊れる、無駄なデータ転送

SELECT * FROM users WHERE id = 1;
SELECT id, name, email FROM users WHERE id = 1;

N+1 クエリ 致命

ループ内でクエリ。100件で101回SQL。本番で死ぬ

# Rails / ActiveRecord
posts.each { |p| puts p.user.name }  # 各postごとにuserをSELECT
posts = Post.includes(:user)
posts.each { |p| puts p.user.name }  # 1+1回

インデックスの放置

WHERE/JOIN/ORDER BY で使う列にはインデックスを。100万件超で体感差絶大

SQLインジェクション 致命

const sql = "SELECT * FROM users WHERE name='" + userInput + "'";
db.query("SELECT * FROM users WHERE name = ?", [userInput]);
// プレースホルダ必須

Git の罠

force push を main に 致命

他人の履歴を消す。チームの信用も消える。--force-with-leaseでも main 禁止。

1コミットに複数の関心事

レビュー困難、cherry-pick できない、revert で巻き添え

1コミット=1論理的変更。git add -pで分けよ

"WIP" "fix" だけのコミットメッセージ

後から git log で何も分からない。Conventional Commits(feat: / fix: / refactor:)推奨

秘密情報をコミット 致命

API キー・パスワード・.env をコミット。一度公開したら漏洩確定。即ローテートが必要。
.gitignore徹底+pre-commitフックで守る

巨大バイナリのコミット

動画・データセット・ビルド成果物 → git の歴史が肥大化。Git LFSや別ストレージに

Bash の罠

変数を引用符で囲わない 致命

rm $file  # $fileに " /" が入ってたら?
rm "$file"

set -e なし

失敗を見逃して処理が続行。先頭で set -euo pipefail 必須

ls の結果をパース

for f in $(ls *.txt); do ...
for f in *.txt; do ...
# またはfind ... -print0 | xargs -0

useless cat

cat file.txt | grep word
grep word file.txt

C / C++ の罠

NULL チェック忘れ → セグフォ 致命

char* p = malloc(n);
strcpy(p, src);  // mallocが失敗したら?
char* p = malloc(n);
if (!p) { /* エラー処理 */ return -1; }
strcpy(p, src);

free 忘れ(メモリリーク)

毎日少しずつメモリを食う。長時間稼働で死ぬ。valgrind / sanitizer で検出

二重free 致命

未定義動作。脆弱性の温床。free後にポインタをNULLに

strcpy / sprintf 致命

境界チェックなしでバッファオーバーフロー。strncpy / snprintfを使え。C++ならstd::string

raw new / delete

C++ ならstd::unique_ptr /std::shared_ptrでRAII。生ポインタは外向きインターフェースのみ

設計レベルのアンチパターン

循環依存

A→B、B→A。テスト困難、変更が連鎖。インターフェースで切る or 統合する

過剰なDRY(再利用神話)

「重複は悪」を暴走させると、無理な抽象化で逆に複雑化。
WET (Write Everything Twice) が正解の場合もある — Rule of Three

YAGNI 違反(先読みしすぎ)

"You Aren't Gonna Need It"。使いもしない汎用化・設定項目・拡張ポイントは害

抽象化レベルの混在

1関数の中で「DB問い合わせ」と「文字列のtrim」が同居。階層が壊れる。
Single Level of Abstraction原則を守る

Singleton の濫用

テスト不可・暗黙のグローバル状態。DI(依存性注入)で代替を検討

無視される警告

コンパイラ警告・lint警告を放置 → 本物の警告に気づかなくなる。-Werror / --strict

プログラマの十戒

📜 The Programmer's Decalogue

  1. 名前を尊べ — 変数・関数・クラス名はドキュメント
  2. 関数を小さく保て — 1つの仕事だけをする
  3. 例外を握りつぶすな — 知らせ、記録し、伝播させよ
  4. テストせよ — 書いた瞬間から、壊れる瞬間まで
  5. コミットを語らせよ — メッセージは未来へのラブレター
  6. 計測してから最適化せよ — 推測でチューニングするな
  7. マジックを避けよ — 暗黙の動作はバグの温床
  8. 削除を恐れるな — 動かないコードより、消したコードのほうが安全
  9. レビューを受けよ — 1人の脳より2人
  10. 謙虚であれ — 自分のコードは6ヶ月後に他人のコードになる
"単純さは信頼性の前提条件である。"— Edsger Dijkstra
"本物のプログラマはみな、自分が書いたコードを6ヶ月後に読み返したとき『誰だこの馬鹿は』と思う。それを通り抜けて成長する。"— プログラマの格言

"良いコードは、悪いコードを知ることから始まる"