@nqounetです。
「ユーザー登録バリデータで学ぶ責任の連鎖」シリーズの第2回です。
前回は、名前とメールアドレスの検証をシンプルなif文で実装しました。
今回は、パスワード強度、確認パスワードの一致、利用規約への同意、年齢制限など、実際のWebアプリケーションで必要となる検証ルールを追加していきます。そして、検証ルールが増えることでコードがどのように複雑化していくかを体験しましょう。
ユーザー登録フォームの要件追加
プロダクトマネージャーから、以下の追加要件が届きました。
- パスワード必須: パスワードを入力必須にする
- パスワード強度: パスワードは8文字以上、大文字・小文字・数字を含むこと
- 確認パスワード一致: パスワードと確認用パスワードが一致すること
- 利用規約同意: 利用規約に同意していること
- 年齢制限: 18歳以上であること
前回のコードに、これらの検証ルールを追加してみましょう。
検証ルールを追加したコード
前回のvalidate_user関数に、新しい検証ルールを追加します。
| |
実行すると、以下のような結果が表示されます。
| |
動作は正しいのですが、コードを見てください。問題が見えてきませんか。
if文ネストの問題点
先ほどのコードには、いくつかの問題があります。
1. ネストが深くなっている
パスワードの検証部分を見てみましょう。
| |
ifの中にif、その中にさらにif…。これは「ネストが深い」状態です。現時点で3段階のネストがありますが、要件が増えるとさらに深くなります。
2. 検証ルール間の依存関係が暗黙的
確認パスワードのチェックは、パスワード強度チェックを通過した場合のみ実行されます。この依存関係はコードの構造に埋め込まれており、一目ではわかりません。
3. 新しい検証ルールの追加が困難
たとえば「パスワードにはユーザー名を含めてはいけない」というルールを追加する場合、どこに書けばよいでしょうか。既存のelsifチェーンに追加すると、さらにネストが深くなります。
4. テストが困難
この関数をテストするには、すべての分岐パターンを網羅する必要があります。
- 名前が空の場合
- メールアドレスが空の場合
- メールアドレスの形式が不正な場合
- パスワードが空の場合
- パスワードが8文字未満の場合
- パスワードに大文字がない場合
- パスワードに小文字がない場合
- パスワードに数字がない場合
- 確認パスワードが空の場合
- 確認パスワードが一致しない場合
- 利用規約に同意していない場合
- 年齢が空の場合
- 年齢が数値でない場合
- 年齢が18歳未満の場合
- すべて正常な場合
15パターン以上のテストケースが必要です。そして、新しい検証ルールを追加するたびに、テストケースは指数的に増えていきます。
5. 修正時の影響範囲が不明確
「パスワードの最小文字数を8文字から10文字に変更してほしい」という要望が来たとします。変更自体は1行で済みますが、その変更が他の検証ルールに影響しないことを確認するのは容易ではありません。
これは「コードの臭い」である
このような問題を抱えたコードは「コードの臭い(Code Smell)」と呼ばれます。動作はするものの、保守性が低く、バグを生みやすい状態です。
今回のコードには、以下のコードの臭いが含まれています。
| コードの臭い | 該当箇所 |
|---|---|
| 深いネスト(Deep Nesting) | パスワード検証の3段ネスト |
| 長いメソッド(Long Method) | validate_user関数が80行以上 |
| 複雑な条件分岐(Complex Conditionals) | if/elsif/elseの連鎖 |
| 責任の集中(God Function) | 1つの関数が全検証を担当 |
どうすればよいのか
ここまで読んで、「これはまずい」と感じた方も多いのではないでしょうか。実際、このままでは以下のような問題が発生します。
- 新機能追加のたびにバグが発生する
- 修正のたびに他の部分が壊れる
- 新しいメンバーが理解するのに時間がかかる
- テストを書くのが億劫になり、テストが書かれなくなる
では、どうすればこの問題を解決できるのでしょうか。
次回は、この複雑化したコードをリファクタリングし、保守性の高い設計に変更する方法を学びます。
まとめ
- 検証ルールが増えると、if/elseのネストが深くなる
- ネストが深いコードは読みにくく、保守が困難である
- 検証ルール間の依存関係が暗黙的だと、変更時の影響範囲がわからない
- 1つの関数に全ての検証を詰め込むと「God Function」になる
- このような状態は「コードの臭い」と呼ばれ、リファクタリングが必要である
次回予告
次回は、この複雑化したバリデーションコードを「Chain of Responsibility」パターンでリファクタリングします。各検証ルールを独立したクラスに分離し、柔軟に組み合わせられる設計に変更していきましょう。
完成コード
この回の完成コード(複雑化した問題コード)を1つのスクリプトにまとめました。
| |
