言語に依存しないが、毎日どこかで誰かがやらかしている10種
数字の意味が読み手に伝わらず、変更時に影響範囲不明
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);
1つのクラスが何でも知っている=1つの変更で全体が壊れる
UserManager に認証・課金・メール送信・履歴管理が全部あったら危険信号。各責務を別クラスに分けよ。
同じバグを複数箇所に量産。修正漏れの温床
3回コピーしたら関数化/共通化を考える(Rule of Three)
読みにくいコードを生み、しかも本当のボトルネックは別の場所にある
Knuthの言葉:「早すぎる最適化は諸悪の根源」。まず動かす→計測→ボトルネックだけ最適化。
エラーが消え、原因不明の障害になる。デバッグ地獄。
try {
doSomething();
} catch (e) {
// 何もしない
}
try {
doSomething();
} catch (e) {
logger.error("doSomething失敗", { cause: e });
throw new AppError("処理失敗", { cause: e });
}
data, info, manager, util などは何も伝えない。コメントで補おうとする時点で負け
function process(data) { ... }
function calculateMonthlyRevenue(orders) { ... }
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; // 本処理(早期リターンで平坦化)
変更が怖くなり、コードが腐る。直したつもりが別を壊す。
最低でも公開関数にユニットテスト。E2Eで主要フローをカバー。
障害発生時に何が起きたか分からない
構造化ログ(JSON)で「誰が・いつ・何をしようとして・どう失敗したか」を残す
環境差・将来変更に弱い
API キー・URL・DB 接続先 → 環境変数や設定ファイルに分離
if (x == null) ... 0 == "" // true "0" == false // true
if (x === null || x === undefined) ... // または ?? を活用
ホイスティングの混乱、ブロックスコープなし
const をデフォルト、再代入する時のみ let。var は使わない。
async function save() {
db.write(data); // awaitなし → 失敗してもUnhandledPromiseRejection
}
async function save() {
await db.write(data);
}
TypeScriptを使う意味を放棄している
不明なら unknown、設計が決まらないなら型定義を考え直す
依存の整合性を壊す。次の人が泣く
依存衝突は根本解決を。peer依存は明示的に管理。
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
try:
do()
except: # SystemExit, KeyboardInterruptまで捕まえる
pass
try:
do()
except SpecificError as e:
logger.exception(e)
raise
venv / poetry / uv を必ず使う。グローバルに pip install は次のプロジェクトを壊す
if x == None:
if x is None:
列追加でコードが壊れる、無駄なデータ転送
SELECT * FROM users WHERE id = 1;
SELECT id, name, email FROM users WHERE id = 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万件超で体感差絶大
const sql = "SELECT * FROM users WHERE name='" + userInput + "'";
db.query("SELECT * FROM users WHERE name = ?", [userInput]);
// プレースホルダ必須
他人の履歴を消す。チームの信用も消える。--force-with-leaseでも main 禁止。
レビュー困難、cherry-pick できない、revert で巻き添え
1コミット=1論理的変更。git add -pで分けよ
後から git log で何も分からない。Conventional Commits(feat: / fix: / refactor:)推奨
API キー・パスワード・.env をコミット。一度公開したら漏洩確定。即ローテートが必要。
.gitignore徹底+pre-commitフックで守る
動画・データセット・ビルド成果物 → git の歴史が肥大化。Git LFSや別ストレージに
rm $file # $fileに " /" が入ってたら?
rm "$file"
失敗を見逃して処理が続行。先頭で set -euo pipefail 必須
for f in $(ls *.txt); do ...
for f in *.txt; do ... # またはfind ... -print0 | xargs -0
cat file.txt | grep word
grep word file.txt
char* p = malloc(n); strcpy(p, src); // mallocが失敗したら?
char* p = malloc(n);
if (!p) { /* エラー処理 */ return -1; }
strcpy(p, src);
毎日少しずつメモリを食う。長時間稼働で死ぬ。valgrind / sanitizer で検出
未定義動作。脆弱性の温床。free後にポインタをNULLに
境界チェックなしでバッファオーバーフロー。strncpy / snprintfを使え。C++ならstd::string
C++ ならstd::unique_ptr /std::shared_ptrでRAII。生ポインタは外向きインターフェースのみ
A→B、B→A。テスト困難、変更が連鎖。インターフェースで切る or 統合する
「重複は悪」を暴走させると、無理な抽象化で逆に複雑化。
WET (Write Everything Twice) が正解の場合もある — Rule of Three
"You Aren't Gonna Need It"。使いもしない汎用化・設定項目・拡張ポイントは害
1関数の中で「DB問い合わせ」と「文字列のtrim」が同居。階層が壊れる。
Single Level of Abstraction原則を守る
テスト不可・暗黙のグローバル状態。DI(依存性注入)で代替を検討
コンパイラ警告・lint警告を放置 → 本物の警告に気づかなくなる。-Werror / --strict
"良いコードは、悪いコードを知ることから始まる"