Featured image of post 第2回-新しい形式を追加すると大変!条件分岐の悩み - Mooを使ってデータエクスポーターを作ってみよう

第2回-新しい形式を追加すると大変!条件分岐の悩み - Mooを使ってデータエクスポーターを作ってみよう

YAMLやXML形式も追加したらコードが複雑に…。if/elseが肥大化する問題を体感し、なぜリファクタリングが必要なのかを理解します。

@nqounetです。

前回は、if/elseを使ってCSVとJSONの2つの形式でアドレス帳データを出力できるようにしました。

今回は、新しい形式を追加するとコードがどうなるか見ていきましょう。

YAMLとXML形式も追加したい

お客様から「YAML形式とXML形式でも出力したい」というリクエストが来ました。

さっそく対応してみましょう。前回のif/else文に新しい条件を追加します。

	flowchart TD
    A[開始] --> B{format は csv?}
    B -->|Yes| C[CSV出力]
    B -->|No| D{format は json?}
    D -->|Yes| E[JSON出力]
    D -->|No| F{format は yaml?}
    F -->|Yes| G[YAML出力]
    F -->|No| H{format は xml?}
    H -->|Yes| I[XML出力]
    H -->|No| J[エラー]
    
    C --> K[終了]
    E --> K
    G --> K
    I --> K
    J --> K

    style B fill:#fdd
    style D fill:#fdd
    style F fill:#fdd
    style H fill:#fdd
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env perl
use v5.36;
use JSON::PP;
use YAML::Tiny;

# アドレス帳データ
my @contacts = (
    { name => '田中太郎', email => 'tanaka@example.com', phone => '090-1234-5678' },
    { name => '鈴木花子', email => 'suzuki@example.com', phone => '080-2345-6789' },
    { name => '佐藤次郎', email => 'sato@example.com',   phone => '070-3456-7890' },
);

# コマンドライン引数から形式を取得(デフォルトはcsv)
my $format = $ARGV[0] // 'csv';

# 形式に応じて出力を切り替え
if ($format eq 'csv') {
    # CSV形式で出力
    say "name,email,phone";
    for my $contact (@contacts) {
        say "$contact->{name},$contact->{email},$contact->{phone}";
    }
}
elsif ($format eq 'json') {
    # JSON形式で出力
    my $json = JSON::PP->new->pretty->encode(\@contacts);
    print $json;
}
elsif ($format eq 'yaml') {
    # YAML形式で出力
    my $yaml = YAML::Tiny->new(\@contacts);
    print $yaml->write_string;
}
elsif ($format eq 'xml') {
    # XML形式で出力
    say '<?xml version="1.0" encoding="UTF-8"?>';
    say '<contacts>';
    for my $contact (@contacts) {
        say '  <contact>';
        say "    <name>$contact->{name}</name>";
        say "    <email>$contact->{email}</email>";
        say "    <phone>$contact->{phone}</phone>";
        say '  </contact>';
    }
    say '</contacts>';
}
else {
    die "未対応の形式です: $format\n";
}

動作確認

YAML形式で出力:

1
2
cpanm YAML::Tiny  # 初回のみ
perl exporter.pl yaml

出力結果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---
- email: tanaka@example.com
  name: 田中太郎
  phone: 090-1234-5678
- email: suzuki@example.com
  name: 鈴木花子
  phone: 080-2345-6789
- email: sato@example.com
  name: 佐藤次郎
  phone: 070-3456-7890

XML形式で出力:

1
perl exporter.pl xml

出力結果:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<contacts>
  <contact>
    <name>田中太郎</name>
    <email>tanaka@example.com</email>
    <phone>090-1234-5678</phone>
  </contact>
  ...
</contacts>

動きましたね!でも、コードを見てください…

if/elseが肥大化している!

コードが長くなってきました。問題点を整理してみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 ┌──────────────────────────────────────────────────────────────┐
                         exporter.pl                           
 ├──────────────────────────────────────────────────────────────┤
   if (csv)  { CSV出力処理... }                                
   elsif (json) { JSON出力処理... }                            
   elsif (yaml) { YAML出力処理... }       どんどん増える!    
   elsif (xml)  { XML出力処理... }                             
   elsif (tsv)  { ... }                                        
   elsif (markdown) { ... }                                    
   elsif (html) { ... }                                        
   ...                                                         
 └──────────────────────────────────────────────────────────────┘

問題1: コードが長くなる

形式が増えるたびにelsifブロックが増えていきます。今は4形式ですが、TSV、Markdown、HTML…と増えていくと、このファイルは何百行にもなるでしょう。

問題2: 変更時の影響範囲が大きい

CSV出力の処理を修正したいだけなのに、JSON、YAML、XMLの処理も同じファイル内にあります。うっかり別の形式のコードを壊してしまうかもしれません。

問題3: テストが難しい

CSV出力だけをテストしたくても、このファイル全体を読み込む必要があります。

問題4: 新しい形式の追加が怖い

既存のコードに手を入れるので、動いている部分を壊してしまう可能性があります。

これが「コードの臭い」です

このような問題をコードの臭い(Code Smell)と呼びます。

今回のケースは「長いメソッド(Long Method)」や「分岐の多さ(Switch Statements)」という典型的なコードの臭いです。

コードの臭いは、今すぐバグになるわけではありませんが、将来的に問題を引き起こす兆候です。

どうすれば良いのか?

次回から、このコードを改善していきます。

まずは「出力処理を別のクラスに分ける」ことから始めましょう。そうすることで、各形式の処理が独立し、変更や追加がしやすくなります。

今回の完成コード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env perl
use v5.36;
use JSON::PP;
use YAML::Tiny;

# アドレス帳データ
my @contacts = (
    { name => '田中太郎', email => 'tanaka@example.com', phone => '090-1234-5678' },
    { name => '鈴木花子', email => 'suzuki@example.com', phone => '080-2345-6789' },
    { name => '佐藤次郎', email => 'sato@example.com',   phone => '070-3456-7890' },
);

# コマンドライン引数から形式を取得(デフォルトはcsv)
my $format = $ARGV[0] // 'csv';

# 形式に応じて出力を切り替え
if ($format eq 'csv') {
    # CSV形式で出力
    say "name,email,phone";
    for my $contact (@contacts) {
        say "$contact->{name},$contact->{email},$contact->{phone}";
    }
}
elsif ($format eq 'json') {
    # JSON形式で出力
    my $json = JSON::PP->new->pretty->encode(\@contacts);
    print $json;
}
elsif ($format eq 'yaml') {
    # YAML形式で出力
    my $yaml = YAML::Tiny->new(\@contacts);
    print $yaml->write_string;
}
elsif ($format eq 'xml') {
    # XML形式で出力
    say '<?xml version="1.0" encoding="UTF-8"?>';
    say '<contacts>';
    for my $contact (@contacts) {
        say '  <contact>';
        say "    <name>$contact->{name}</name>";
        say "    <email>$contact->{email}</email>";
        say "    <phone>$contact->{phone}</phone>";
        say '  </contact>';
    }
    say '</contacts>';
}
else {
    die "未対応の形式です: $format\n";
}

まとめ

  • 新しい形式を追加するとif/elseが肥大化することを体験しました
  • コードが長くなると、保守性やテスト容易性が低下します
  • このような問題を「コードの臭い」と呼びます
  • 次回から、この問題を解決するためのリファクタリングを始めます

次回「第3回-出力処理を専用クラスに分けよう」では、出力処理を専用クラスに分けていきます。お楽しみに!

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。