sironekotoroの日記

Perl で楽をしたい

「良いコード/悪いコードで学ぶ設計入門」第9章 #ミノ駆動本

ここはコードがほとんどないというか、あっても見てすぐに動作が予測できるものばかりなので、さらっと。

9.1 デッドコード

または到達不能コード。

本文では絶対に通らない else 文の例で表されている。

コードの可読性を損なうので、削除。

9.2 YAGNI原則

「こんなこともあろうかと」

真田さんだ。

ja.wikipedia.org

「こんなこともあろうかと」というセリフが代名詞として各種媒体で多用されている[7][8]が、実際に本編中でこのセリフを発したことは『宇宙戦艦ヤマト 完結編』までのシリーズ作品では1回もなく、『ヤマト2』第10話で「たぶんこんなこともあろうと思って〜」という似たセリフを発したのみである。

え、そうなの!?

それはともかく、将来の仕様を予見して実装しておくのは無駄だし、デッドコードになるからやめておけ、と。

9.3 マジックナンバー

コード中に、意図が不明な数字や文字列を直接書かない、と。

これは意図して、constance とか、変数名を全部大文字で MINIMUM_VALUE みたいにするのが鉄板よね。

同一のマジックナンバーは複数の箇所で実装されがちで、重複コードを生みます

そして、第6章の同じような switch 文が複数箇所に書かれちゃうという問題と類似の事態になると。

9.4 文字列型執着

// ラベル文字列、表示色(RGB)、上限文字数
String title = "タイトル,255,255,240,64"

おお、これはひどい

結局、これを使う段になったら split で分割した上で取り出さなきゃならんし面倒よなぁ。

メリットは・・・変数ひとつで済むとか?

いや、だったらハッシュにしたほうが。

9.5 グローバル変数

使わない方が良い、というのは知ってる。

設計が不十分なシステムでは、巨大データクラスが非常に生み出されやすいです。グローバル変数を使っていなくとも、グローバル変数と同質のものを知らず知らずのうちに使っているのです。

これは陥ってる可能性がありそう。

9.6 null問題

リスト 9.6

Perl 学んでて、null 意識することはなかったなぁ・・・というのは、Perl の場合は undef なので。

ここクリックして展開

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;
use Function::Parameters;

package Equipment {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has defence => ( is => 'ro', default => undef );
    __PACKAGE__->meta->make_immutable();
}

package Member {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;
    use constant { MIN => 0 };

    has head    => ( is => 'ro', default => undef );
    has body    => ( is => 'ro', default => undef );
    has arm     => ( is => 'ro', default => undef );
    has defence => ( is => 'ro' );

    method total_defence() {
        my $total = $self->defence;
        $total += $self->head->defence;
        $total += $self->body->defence;
        $total += $self->arm->defence;
        return $total;

    }
    __PACKAGE__->meta->make_immutable();
}

package main;

my $iron_helm = Equipment->new( defence => 10 );
say $iron_helm->defence;    # 10;

my $member = Member->new();
say $member->total_defence;  # Can't call method "defence" on an undefined value

まぁ、エラーでるよね。

デフォルトが undef にしていると、そのまま表示して良いかとかにいちいちチェックが必要になっちゃうと。

Perl はコンテキストに沿って undef を 0 とか "" に読み替えてくれる。

use strict;
use warnings;
use feature qw/say/;

my $num = undef;
$num = +1;
say $num;    # 1

my $str = undef;
$str .= "hello";    # . は文字列連結演算子
say $str;           # hello

リスト 9.11 「装備なし」をnullでない方法で実現

装備するってメソッドを、インスタンス変数を不変にするというルールを設けて実装してみた。

ここクリックして展開

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;
use Function::Parameters;

package Equipment {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has name          => ( is => 'ro', default => '装備なし' );
    has price         => ( is => 'ro', default => 0 );
    has defence       => ( is => 'ro', default => 0 );
    has magic_defence => ( is => 'ro', default => 0 );

    method empty() {
        return $self->new();
    }

    __PACKAGE__->meta->make_immutable();
}

package Member {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has head    => ( is => 'ro', default => sub { Equipment->empty() } );
    has body    => ( is => 'ro', default => sub { Equipment->empty() } );
    has arm     => ( is => 'ro', default => sub { Equipment->empty() } );
    has defence => ( is => 'ro', default => 0 );

    method total_defence() {
        my $total = $self->defence;
        $total += $self->head->defence;
        $total += $self->body->defence;
        $total += $self->arm->defence;
        return $total;
    }

    method take_off_all_equipments() {
        $self->head( Equipment->empty );
        $self->body( Equipment->empty );
        $self->arm( Equipment->empty );
    }

    method equip(%hash) {

        return Member->new(
            head => $hash{head},
            body => $hash{body},
            arm  => $hash{arm},
        );

    }

    __PACKAGE__->meta->make_immutable();
}

package main;

my $member = Member->new();
say $member->head->name;       # 装備なし
say $member->total_defence;    # 0

my $iron_helm  = Equipment->new( name => 'iron_helm',  defence => 10 );
my $iron_armor = Equipment->new( name => 'iron_armor', defence => 10 );
my $empty_arm  = Equipment->empty;

my $equiped_member =
  $member->equip( head => $iron_helm, body => $iron_armor, arm => $empty_arm );

say $equiped_member->total_defence();    # 20;

9.7 例外の握り潰し

Perl の場合、 use strict;, use warnings; つけておけば、意図しない限りは例外握り潰せない。

意図するなってことね・・・。

例外をキャッチしたときには、通知や記録、場合によってはリカバリ処理を実行します。

#!/usr/bin/env perl
use v5.36;
use feature qw/try/;
no warnings "experimental::try";

try {
    die("突然の死");  # 例外出して死ぬ
}
catch ($e) {

    # 何も表示されず終了する
}

9.8 設計秩序を破壊するメタプログラミング

まぁ、難しそうだし使うことないよなー・・・というのはあれで、リフレクションの仕方がわからなかっただけです。

例示コードだけ書いておきます。

ここクリックして展開

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

package Level {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;
    use constant { MIN => 1, MAX => 99 };

    has value => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        trigger  =>
          sub { croak "out of range." if ( $_[1] < MIN || MAX < $_[1] ) },
    );

    sub initialize {
        return Level->new( value => MIN );
    }

    sub increase {
        my $self  = shift;
        my $value = $self->value;
        if ( $value < MAX ) {
            return Level->new( $value + 1 );
        }
    }
    __PACKAGE__->meta->make_immutable();
}

package main;

my $one = Level->new( value => 1 );
say $one->value;
my $ninty_nine = Level->new( value => 99 );
say $ninty_nine->value;

9.9 技術駆動パッケージング

Web アプリでお馴染みの MVC アーキテクチャによるフォルダ分け。

これを、「設計クラスが似ている」という理由でビジネスクラスに適用してフォルダ分けしちゃうと混乱するよ、というお話。

Perl で Web アプリといえば Mojolicious !ということで、やってみます。

まずはインストール。

cpanm Mojolicious

雛形作成。今回はいつもの lite_app ではなく、lite_app なし、つまり本格アプリつくるときの構成でいきます。

いつも lite_app ばかりだったので、実は初めて。

# mojo generate app

雛形作成後のディレクトリ構造はこんな感じ

$ tree
.
├── lib
│   ├── MyApp
│   │   └── Controller
│   │       └── Example.pm
│   └── MyApp.pm
├── my_app.yml
├── public
│   └── index.html
├── script
│   └── my_app
├── t
│   └── basic.t
└── templates
    ├── example
    │   └── welcome.html.ep
    └── layouts
        └── default.html.ep

この lib/MyApp/Controller 配下に、 Order(注文), Payment(支払い), Stock(在庫) とフォルダを作ります。

これが本文中では図 9.4 にあたる「ビジネス概念の種類ごとにフォルダ分け」です。

$ tree
.
├── lib
│   ├── MyApp
│   │   └── Controller
│   │       ├── Example.pm
│   │       ├── Order
│   │       │   └── Example.pm
│   │       ├── Payment
│   │       └── Stock
│   └── MyApp.pm
├── my_app.yml
├── public
│   └── index.html
├── script
│   └── my_app
├── t
│   └── basic.t
└── templates
    ├── example
    │   └── welcome.html.ep
    └── layouts
        └── default.html.ep

今回は Order だけにコントローラーを追加してみます。

  1. lib/MyApp/Controller/Example.pmlib/MyApp/Controller/Order 配下にコピー

  2. コピーした lib/MyApp/Controller/Order/Example.pm の1行目、package のところを package MyApp::Controller::Order::Example; と実際のフォルダ構造に合わせて編集

  3. lib/MyAppp.pm にルーティング追加

package MyApp;
use Mojo::Base 'Mojolicious', -signatures;

# This method will run once at server start
sub startup ($self) {

    # Load configuration from config file
    my $config = $self->plugin('NotYAMLConfig');

    # Configure the application
    $self->secrets( $config->{secrets} );

    # Router
    my $r = $self->routes;

    $r->get('/')->to( controller => 'Example', action => 'welcome' );

    # 以下5行を追加
    $r->get('/order')->to(
        controller => 'Controller::Order::Example',
        action     => 'welcome',
        template   => 'example/welcome',
    );
    # 追加ここまで
}

1;

Mojoliciou 付属のWebサーバアプリ morbo で起動します。

morbo script/my_app

これで、http://localhost:3000/order にアクセスしても、トップページと同じ画面が出るようになりました。

このように、ビジネス概念ごとにフォルダを分け、その中に関連するコントローラを集めていけば、修正や追加も容易となるのは明白ですね。

今回は結構勉強になりました。

自分の要求範囲だと、lite_app 程度のもので十分で、lite_app なしで雛形作ったのも、そこでルーティング追加したのも初めてでした。

勉強駆動というか、本駆動でやることが広がるのはいいですね。

9.10 サンプルコードのコピペ

サンプルコートはあくまで言語仕様やライブラリの機能性を説明するためにかかれたものです。

うちはよくサンプルコードを書くのですが、これは本当にそうで、使われる場所の保守性や変更容易性は考えたことがないです。

あくまで、その関数やライブラリがどのような働きをするか、のみに絞って書くことがほとんどです。

ということで、サンプルコードはあくまでサンプル。

あるべきクラス構造を設計しましょう。

ところで、脚注にでてきた「サンプルコードのコピペだけで製品を動かしてしまう「コピペ職人」、逆にすごいのでは。

何をコピペしたら動くのか知っているわけで・・・まぁ、設計品質に期待できないのはそのとおりなのですが。

9.11 銀の弾丸

そんなものはない。

というのはちょっと長く IT 業界にいるとわかるのではないかなぁ、と思います。

4, 5 年で移り変わる主流の言語、フレームワーク・・・

しかし、そこを乗り切っている考え方はあります。

設計に best はありません。常に better を目指しましょう。

はい。