3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【メモ】DRY原則は本当に正しいか?フロントエンド実装で迷った時に思い出すAHA

Posted at

普段フロントエンド実装をしてて、
「あれ、このコード共通化するかー、いやあえてコピペで済ませるべき?」と手が止まることがあります。

以前は「コード重複は悪! 全部共通化だ!」と思ってた時期もあったんですが、
そうじゃないな、と感じる場面は多いです。

今回は、その辺りの葛藤と、今では意識している「バランス」について、自戒を込めて整理してみようと思います。

(ぶっちゃけ、個人開発のコードはコピペ三昧です)

そもそも「DRY原則」ってのは?

まあ、これはもうエンジニアなら耳にタコができるくらい聞いてますかね。
Don't Repeat Yourself(繰り返しを避けよ)」ってやつ。

『達人プログラマー』という本で提唱された原則ですが、
基本的には「システム内の知識は、単一の、曖昧さのない、信頼できる表現を持たなければならない」というものです。

同じ計算ロジックがあちこちに散らばってたら、税率が変わったときに大量の修正と修正漏れで●にますよね。

例(JavaScript)

 悪い例 同じ処理を繰り返してしまっている
// 商品の税込価格を計算する処理が重複している
const priceA = 1000;
const taxA = priceA * 0.1;
const totalA = priceA + taxA;

const priceB = 2500;
const taxB = priceB * 0.1;
const totalB = priceB + taxB;

console.log(totalA); // 1100
console.log(totalB); // 2750
  • 何度も「税込計算」を書いてしまっている
  • 税率を変えたいときには、すべての箇所を書き直す必要があり、バグの温床になる
  • 明確なDRY原則違反
 良い例 処理を共通化してDRYを実現
// 税込価格を計算する関数を作り、繰り返しを排除する
function calcWithTax(price, taxRate = 0.1) {
  return price + (price * taxRate);
}

console.log(calcWithTax(1000));   // 1100
console.log(calcWithTax(2500));   // 2750
  • 繰り返しロジックは関数化し 1 箇所にまとめる
    → 税率変更も関数だけ修正すれば OK
  • 保守性・拡張性が向上

こういうのはいいんです。
ただ、私が以前やったのは、これを「ロジック」だけじゃなく「UIコンポーネント」にも過剰に適用しちゃった時なんですよね。

共通化依存。そして伝説へ⋯

ReactやVueでコンポーネントを作っていると、似たようなボタンやカードパーツが出てきます。
「お、これデザイン似てるし、共通コンポーネントにしちゃえ!」って勢いでまとめた結果、後に面倒なことに⋯。

最初は良かったんですが、後々仕様変更で
「この画面の時だけ、アイコンを左に寄せたい」
「このときでは文字色を赤に」

みたいな要望が重なってきて...。

結果、こんな感じの「神コンポーネント」が爆誕しました。
(いわゆるProps Drillingや低凝集ってやつです)

// 😱 実際にやってしまった感じのコード(イメージ)
const UniversalButton = ({ 
  label, 
  onClick, 
  isPrimary, 
  isDestructive, 
  hasIcon, 
  iconPosition, // 'left' or 'right'
  overrideColor, // ここら辺から怪しい
  isLoading,
  ...props 
}) => {
  // 複雑な条件分岐の嵐...
  if (isPrimary && !isDestructive) { ... }
  if (overrideColor) { ... }
  
  return ( ... );
}

これ、使う側も props の組み合わせを考えるだけで一苦労ですよね。

「共通化したはずが、逆にメンテナンスコストが上がってる」という本末転倒な状態。

リファクタリングしたいと思いつつ、既に動いているシステムのため、
「これなら別々に書いとけばよかった...」と毎回思ってました。

そこで出会った「AHA原則」

そんな時に知ったのが 「AHA(Avoid Hasty Abstractions⋯早すぎる抽象化を避けよ)」 という考え方です。

ざっくり言うと 「早すぎる間違った抽象化をすると、かえって修正が複雑になるので、それなら重複してる方がマシ」 というスタンスです。

この説明を見て、迷ったときは(推奨ではないけど)コピペしてもいいんだなって思いました。

だけど、この原則で思うことは
「コードの見た目が似ているという理由だけでまとめるのは間違い」ということです。
むしろ、本当に抽象化すべきかどうかは「同じ意味の処理かどうか」まで深く理解して判断すべきなんですよね。

ぶっちゃけ、間違った抽象化は、後からコピペまとめより何倍も大変です。

何でもかんでもコピペOKってわけじゃないんですが、このAHAを意識するようになってから、共通化の基準が明確になり、個人的には設計がだいぶ楽になったと思います。

  1. 変更のタイミングが同じか?
  2. 変更の理由が同じか?

たまたま見た目が似ていても、「キャンペーン用バナー」と「通常の商品カード」だと変更されるタイミングも理由も全然違いますよね。
これらを無理にDRYにしてしまうと、片方の変更がもう片方に予期せぬ影響を与えたりします。
(いわゆる「結合度が高まる」ってやつ。)

なので、「これらは偶然似ているだけなのか? それとも本質的に同じなのか?」を考えるようにしています。

答えがわからないうちは、とりあえずベタ書き(複製)しておいて、「3回くらい同じ重複コードが出てきたら、そこで共通化を検討する」くらいで丁度いいのかもしれません。
もちろん個人開発なのかチーム開発なのか、プロジェクトルールは優先すべきです。

個人的なまとめ(と自戒)

結局のところ、DRY原則もAHA原則も、常にどちらかであるって絶対的な正解はないです。

私が個人的に思うのは、
「これは本質的に同じ知識・責務なのか?」と考える意識が大事、ということです。

原則 意識すべきこと(判断基準) 目的とする設計
DRY (Don't Repeat Yourself) 変更の理由が同じなロジックは徹底的に集約する。 (特にビジネスロジック) 高凝集低結合の実現
AHA (Avoid Hasty Abstractions) 変更の理由が異なるなら、見た目が似ていても安易にまとめない。 不必要な結合(低凝集)の回避

特に、フロントエンドのコンポーネント開発では、見た目の類似性に惑わされて変更の理由が違うものまで一つの引数だらけのコンポーネントに押し込めてしまうのが、一番危険な落とし穴だと思ってます。

結論として、「こうすれば確実」という銀の弾丸はないんですが、

  • 実装に取り掛かる前に、「このコンポーネントを将来変更するとしたら、それはどんな理由だろうか?」と考える。
  • そして、少しでも迷ったら、「間違った抽象化をするリスク」を思い出し、あえてコピペを選択する勇気を持つこと。

常に完璧な判断なんて難しいので、この2つの意識を持つだけでも、メンテナンス性の低いコードを生み出す確率減るんじゃないかな、というのが、個人の見解です。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?