- 明日以降、Perlの言語自体にはまらない
- 今日、いろいろやって、なるべくはまってください
- 疑問があったらどんどん質問してください
- Perlプログラミング勘所
- Perlによるオブジェクト指向プログラミング
- テストを書こう
- ヒント
- 課題について
- Perlでプログラミングをしたことがありますか?
-
事前課題
-
前提
- はじめてのPerl、続はじめてのPerlに目を通している
- 一度はPerlでオブジェクト指向プログラミングしたことがある
- 事前課題でやっているはず
- CPAN
- やりたいことがすでにモジュール化されていることが多い
- それCPANでできるよ
- 表現力が高い
- TMTOWTDI (やりかたはいくつもあるよ!)
- 実際に使われてる
- はてな/DeNA/LINE/mixi
- 良い開発文化がある
- http://perldoc.jp/ : perldocの日本語訳
- https://metacpan.org/ : CPANの検索
- Perlでおさえておきたい/よくはまるポイントを説明
- use strict; use warnings;
- use utf8;
- データ型
- コンテキスト
- リファレンス
- パッケージ
- サブルーチン
- ファイルの先頭には必ず書きましょう
use strict;
use warnings;
- デフォルトの振る舞いは互換性のために制限が弱い
my $message = "hello";
print $messsage; # typo!
# => エラーにならない!
$messagge = "bye"; # typo!
# => $messagge がグローバル変数になる!
- 細かな振る舞いはperldoc参照
$ perldoc strict
$ perldoc perllexwarn
Perl での文字コードの扱いについて
- Perl では文字列は以下のどちらかで表されます
- Perl の内部表現に変換された文字列
- バイト列
次の内容を UTF-8 でファイル(hoge.pl)に保存します
print 'ほげ';
実行します
$ perl hoge.pl
ほげ
ええやん
length は文字数をカウントする関数
print length 'ほげ';
実行します
$ perl hoge.pl
6
あかんやん
- 'ほげ' はマルチバイト文字
- 何もしないと Perl は 'ほげ' をバイト列とみなす
- UTF-8 の 'ほげ' は 6 バイトなので length 'ほげ' は 6 になる
- 文字数数えるのに困る
use utf8;
print length 'ほげ';
$ perl hoge.pl
2
ええやん!
- utf8 プラグマをつけると、 Perl はコード内のマルチバイト文字を UTF-8 の文字列として解釈し、Perl 内部表現の文字列に変換する
- UTF-8 で記述された 'ほげ' を UTF-8 として解釈すると当然 2 文字なので、length 'ほげ' は 2 になる
めでたしめでたし!!
use utf8;
print 'ほげ';
$ perl hoge2.pl
Wide character in print at hoge2.pl line 2.
ほげ
なんか出た…
'ほげ' は Perl 内部表現に変換されているので、そのまま Perl の世界の外に出そうとすると怒られる 再びバイト列に変換してあげる必要がある
use utf8;
use Encode;
print encode_utf8 'ほげ';
これでOK
- まとめると
- ファイルは UTF-8 で保存する
- use utf8; プラグマを忘れないように
- 難しかったらこれだけ守って下さい
- strict と warnings も忘れずに
- スカラ
- 配列
- ハッシュ
- (ファイルハンドル)
- (型グロブ)
- 1つの値
- 文字列/数値/リファレンス
- $calar と覚えましょう
my $scalar1 = 'test';
my $scalar2 = 1000;
my $scalar3 = \@array; # リファレンス(後述)
- @rray と覚えましょう
my @array = ('a', 'b', 'c');
- Q: @array の二番目の要素を取得するには?
print $array[1]; # 'b'
- スカラを取得するので$でアクセス
- $array と @array とは別の変数
$array[0]; # get
$array[1] = 'hoge'; # set
my $length = scalar @array; # 長さ
my $last_ids = $#array; # 最後の添字
my @slice = @array[1..4]; # スライス
for my $e (@array) { # 全要素ループ
print $e;
}
- チェックしておこう!
- 関数: push/pop/shift/unshift/map/grep/join/sort/splice
perldoc -f push
- モジュール: List::Util/List::MoreUtils/List::UtilsBy
- インストールしているなら
perldoc List::Util
- していなかったら metacpan で検索
- インストールしているなら
- %ash とおぼえましょう
my %hash = (
perl => 'larry',
ruby => 'matz',
);
- Q: hash の key
perl
に対応する値を取得するには?
print $hash{perl}; # larry
print $hash{ruby}; # matz
- スカラを取得するので$
- $hash と %hash は別の変数
- {} の中は裸の文字列( =
'
,"
がない)が許される
$hash{perl}; # get
$hash{perl} = 'larry'; # set
for my $key (keys %hash) { # 全要素のキー
my $value = $hash{$key}; # キーで各要素を get
}
- チェックしておこう!
- 関数: keys/values/delete/exists
perldoc -f keys
- 関数: keys/values/delete/exists
- スカラ $ / 配列 @ / ハッシュ %
- $val と @val と %val は別の変数
$ perldoc perldata
- Perlといえばコンテキスト
- 代表的ハマリポイント
- 式が評価される場所( = コンテキスト)によって結果が変わる
my @x = (0, 1, 2);
my ($ans1) = @x;
my $ans2 = @x;
- それぞれ何が入っているでしょうか?
my @x = (0, 1, 2);
my ($ans1) = @x; # => 0
my $ans2 = @x; # => 3
- 配列への代入の右辺はリストコンテキスト
- 0 が代入される。1,2 は捨てられる
- スカラへの代入の右辺はスカラコンテキスト
- 配列はスカラコンテキストで評価すると長さが返る
- <ここ> のコンテキストはスカラ? リスト?
sort <ここ>;
length <ここ>;
if (<ここ>) { }
for my $i (<ここ>) { }
$obj->method(<ここ>);
my $x = <ここ>;
my ($x) = <ここ>;
my @y = <ここ>;
my %hash = (
key0 => 'hoge',
key1 => <ここ>,
);
scalar(<ここ>);
<ここ>;
- 式/値が評価される場所によって結果がかわる
- コンテキストの決まり方は基本的に覚えるしかない
- 組み込み関数に注意(length など)
- 組み込み関数以外も prototype という機能で実現可能なので注意
- 関数の側からは wantarray で今呼ばれているコンテキストを知ることができます
$ perldoc perldata
$ perldoc perlsub # Prototypes の章
$ perldoc -f wantarray
- スカラ/配列/ハッシュ などへの参照
- C++とかの参照/Rubyなどではすべて参照
- データ構造を作るときに重要
- 行列をつくろう
my @matrix = (
(0, 1, 2, 3),
(4, 5, 6, 7),
);
- どうなるでしょうか…
my @matrix =
(0, 1, 2, 3, 4, 5, 6, 7);
- ひー
my %entry = (
body => 'hello!',
comments => ('good!', 'bad!', 'soso'),
)
- どうなるでしょうか…
my %entry = (
'body' => 'hello!',
'comments' => 'good',
'bad!' => 'soso',
);
- ひー
- () の中はリストコンテキスト
- リストコンテキスト内ではリストは展開される
my @matrix = (
(0, 1, 2, 3),
(4, 5, 6, 7),
);
my @x = (1, 2, 3);
my $ref_x1 = \@x;
# 略記法
$ref_x2 = [1, 2, 3];
# 組み合わせ
$ref_x3 = [@x];
my $ref_x = [1, 2, 3];
my @x = map { $_ * 2 } @$ref_x;
print $ref_x->[0]; # 1
my @new_x = @$ref_x;
print $new_x[0]; # 1
my %y = (
perl => 'larry',
ruby => 'matz',
);
my $ref_y1 = \%y;
# 略記法
$ref_y2 = {
perl => 'larry',
ruby => 'matz',
};
my $ref_y = {
perl => 'larry',
ruby => 'matz',
};
my @keys = keys %$ref_y;
print $ref_y->{perl}; # larry
my %new_f = %$ref_y;
print $new_f{perl}; # larry
- リファレンスはスカラ値 = リストコンテキストで展開されない
my $matrix = [
[0, 1, 2, 3],
[4, 5, 6, 6],
];
my $entry = {
body => 'hello!',
comments => ['good!', 'bad!', 'soso'],
};
- 例: リファレンスを返すメソッドの返り値をデリファレンス
my $result = [
map {
$_->{bar};
}
@{ $foo->return_array_ref }
# ↑ レースを使う
];
- 基本的にリファレンス以外使わない
- ハマリにくい
my @foo = (1, 2, 3);
my %foo = (a => 1, b => 2, c => 3);
$foo[1], $foo{a}
# ↑ 同じ変数を参照しているように見える……
# が実際は違う変数
my $foo = [1, 2, 3];
my $foo = {a => 1, b => 2, c => 3};
# ↑ 同じ変数なので warning がでる
必要なときだけデリファレンスする
my $list = [1, 2, 3];
push @$list, 4;
- サブルーチンの引数の処理(後述)
- 多値を返すとき
sub hello {
my ($arg1, $arg2, %other_args) = @_;
return ($arg1, $arg2);
}
my ($res1, $res2)
= hello('hey', 'hoy', opt1 => 1, opt2 =>2);
- スカラ/配列/ハッシュへの参照
- 複雑なデータ構造を扱うときに必須
- 記法がちょっと複雑
$ perldoc perlreftut
$ perldoc perlref
- 名前空間
- モジュールロードのしくみ
- クラス(後述)
package Hoge;
our $PACKAGE_VAL = 10;
# $HOGE::PACKAGE_VAL == 10
sub fuga {
}
# Hoge::fuga();
1;
use My::File;
# => My/File.pm がロードされる
- @INC(グローバル変数)に設定されたパスを検索
use lib 'path/to/your/lib';
$ perl -Ipath/to/your/lib;
- path/to/your/lib/My/File.pm をさがしてあれば読み込む
hello() # 定義前に括弧なしで呼ぶにはは & がいる
sub hello {
my ($name) = @_;
return "Hello, $name";
}
hello();
hello; # 定義後であれば括弧は省略可能
sub func1 {
my ($arg1, $arg2, %args) = @_;
my $opt1 = $args{opt1};
my $opt2 = $args{opt2};
}
func1('hoge', 'fuga', opt1 => 1, opt2 => 2);
sub func2 {
my $arg1 = shift; # 暗黙的に@_を処理(破壊的)
my $arg2 = shift;
my $args = shift;
my $opt1 = $args->{opt1};
my $opt2 = $args->{opt2};
}
sub func3 { shift->{arg1} }
sub func4 { $_[0]->{arg1} } # @_ の第0要素
- パッケージに定義される
package Greetings;
sub hello { }
1;
# hello は Greeting::hello(); として定義される
- ネストしてもパッケージに定義されるので注意
package Greetings;
sub hello {
sub make_msg { }
sub print {}
print (make_msg() );
}
1;
# Greeting::hello();
# Greeting::make_msg();
# Greeting::print();
- package が無いときは main パッケージ
sub hello { }
# main::hello()
- オブジェクト指向プログラミングしたことありますか?
- Perl以外でも
- 念のためポイントだけおさえておきます
- 抽象化とは
詳細を捨象し、一度に注目すべき概念を減らすことおよびその仕組み
抽象化 (計算機科学) より
- 一度に考えないといけないことを減らす
- = スコープをせばめる
- 保守性/再利用性を高める
-
gotoプログラミング
- 制御のながれがすべて自由
-
Perl で gotoプログラミングした例(fizzbuzz)
my $i = 1;
START:
goto "END" if $i > 35;
goto "PRINT_FIZZBUZZ" if $i % 15 == 0;
goto "PRINT_FIZZ" if $i % 3 == 0;
goto "PRINT_BUZZ" if $i % 5 == 0;
goto "PRINT_NUM";
PRINT_NUM:_
print $i;
goto "PRINT_NL";
PRINT_FIZZ:_
print "fizz";
goto "PRINT_NL";
PRINT_BUZZ:_
print "buzz";
goto "PRINT_NL";
PRINT_FIZZBUZZ:_
print "fizzbuzz";
goto "PRINT_NL";
PRINT_NL:_
print "\n";
$i++;
goto "START";
END:
- 制御の流れが分かりにくい
- プログラム全体を一度に理解していないといけない
- 大規模なソフトウェアになると保守できなくなる
- コードの再利用は実質的にはできない
EW Dijkstra(1968). Go to statement considered harmful
- 手続きを逐次、選択、繰り返しで表現
- サブルーチンにより手続きを抽象化
sub fizzbuzz {
my $i = 1;
while ($i < 35) {
if ($i % 15 == 0) {
print "fizzbuzz";
}
elsif ($i % 3 == 0) {
print "fizz";
}
elsif ($i % 5 == 0) {
print "buzz";
}
else {
print $i;
}
print "\n";
$i++;
}
}
fizzbuzz();
- 手続きとデータがばらばら
open my $fh, '<', $filename;
while (my $line = readline($fh)) {
print $line;
}
close $fh;
- $fhに対してどのような操作ができるのか?
- open/close/readline はどんなデータを操作できるのか?
- データと手続きそれぞれのスコープが広い
-
オブジェクトによる抽象化
-
オブジェクトとは
- プログラムの対象となるモノ
- データ + 手続き
-
プログラムはオブジェクト同士の相互作用
-
"どう"ではなく"なにがどうする"に着目する
- 処理の対象(データ)と処理の内容(手続き) が結びついている
- オブジェクトごとにコードを理解できる
- 再利用しやすい
- オブジェクトというメタファが人間にとってわかりやすい (こともある)
- => 設計しやすい
use IO::File;
my $file = IO::File->new($filename, 'r');
while (my $line = $file->getline) {
print $line;
}
$flie->close;
- $fileに対してできる操作はすべてメソッドとして定義されている
- オブジェクト間の相互作用/メッセージパッシング
- オブジェクト間の相互作用を表現しやすいようにプログラミング言語が支援
- クラスとインスタンス
- カプセル化
- 継承
- ポリモーフィズム
- ダイナミックバインディング
クラス: [ データ構造定義 + 手続定義 ]
↓ 生成
インスタンス: [ { データ } + 手続への参照 ]
OOP概念 | Perlでの実装 |
---|---|
クラス | パッケージ |
メソッド | パッケージに定義された手続き |
オブジェクト | 特定のパッケージにbless()されたリファレンス |
- 課題ででた Parser クラス(簡易版)
# パッケージに手続きを定義
package Parser; # クラス名
use strict;
use warnings;
# 続く
1;
# コンストラクタ
# Parser->new; のように呼び出す
sub new {
my ($class, %args) = @_; # クラス名が入る
return bless \%args, $class;
}
# $parser->parse(filename => 'hoge.log'); のように呼び出す
sub parse {
my ($self, %args) = @_;
my $filename = $args{filename};
...
}
use Parser;
my $parser = Parser->new;
$parser->parse(filename => 'hoge.log');
- コンストラクタは自分で定義する
- (blessされた)オブジェクトも自分で作る
- new()
- リファレンス を パッケージ(クラス) で bless して返す
- blessはデータと手続きを結びつける操作
my $self = bless { filename => 'hoge.log' }, 'Parser';
- 文法的違いはない
- 定義時: 第一引数を$classとみなすか$selfとみなすか
- 呼出時: クラスから呼び出すかインスタンスから呼び出すか
# この二つが等価
Class->method($arg1, $arg2);
Class::method('Class', $arg1, $arg2);
# この二つが等価
$object->method($arg1, $arg2);
Class::method($object, $arg1, $arg2);
- 1インスタンスに付き1データ(のリファレンス)
- 複数のデータをもちたい場合はハッシュをbless する
my $self = bless {
filed1 => $obj1,
field2 => [],
field3 => {},
}, $class;
- 可視性の指定(public/privateなど) はない
- すべてが public
- 命名規則などでゆるく隠蔽する
sub public_method {
my $self = shift;
}
sub _private_method {
my $self = shift;
}
- 完全に隠蔽する方法もある(クロージャを使う)
- use parent を使う
package Me;
use parent 'Father';
1;
- 親クラスのメソッド
- SUPER
sub new {
my ($class) = @_;
my $self = $class->SUPER::new();
return $self;
}
- Mixinをやりたいときなどにつかう
- 乱用しない
package Me;
use parent qw(Father Mother); # 左 => 右の順
1;
- メソッドの検索アルゴリズム
- Class::C3
- NEXT
-
手作り感あふれるオブジェクト指向
- package に手続きを定義
- blessでデータと結びつける
- コンストラクタは自分でつくる、オブジェクトも自分で作る
- オブジェクト指向風によびだせるような糖衣
-
オブジェクト指向に必要な機能はそろっている
- JavaでいうObjectのようなもの
- UNIVERSALに定義するとどのオブジェクトからも呼べる
- isa()
my $dog = Dog->new;
$dog->isa('Dog'); # true
$dog->isa('Animal'); # true
$dog->isa('Man'); # false
- can()
my $bark = $dog->can('bark');
$man->$bark();
-
呼び出されたメソッドがMy::Classクラスに見つからない場合、
- My::Class::AUTOLOADメソッドを探す
- 親クラスのAUTOLOADメソッドを探す
- UNIVERSAL::AUTOLOADを探す
- なかったらエラー
-
AUTOLOADメソッドで未定義のメソッド呼び出しを補足
-
Ruby の method_missing
- フィールドを動的に定義できたりする
- 想像できない振る舞いを作り出し得るのでなるべく使わない
- こういう仕組みがあることは理解しておく
package Foo;
sub new { bless {}, shift }
our $AUTOLOAD;
sub AUTOLOAD {
my $method = $AUTOLOAD; # 呼び出そうとしていたメソッド名
return if $method =~ /DESTROY$/;
$method =~ s/.*:://;
{
# 定義してやる
no strict 'refs';
*{$AUTOLOAD} = sub {
my $self = shift;
sprintf "%s method was called!", $method;
};
}
# 呼び出す
goto &$AUTOLOAD;
}
1;
- 想像できない振る舞いを作り出し得るのでなるべく使わない
- こういう仕組みがあることは理解しておく
- URI
my $uri = URI->new('http://exapmle.com/');
$uri->path('hoge');
print "URI is $uri"; # 'URI is http://exapmle.com/hoge'
- DateTime
$new_dt = $dt + $duration_obj;
$new_dt = $dt - $duration_obj;
$duration_obj = $dt - $new_dt;
for my $dt (sort @dts) { # sort内で使われる<=>がoverloadされている
...
}
-
Perlのオブジェクト指向は手作り感満載
- newは自分でつくる
- フィールドのアクセサも自分で定義
-
たいへんなので自動化されている
- 継承ツリーを汚さない
- おすすめ
package Foo;
use Class::Accessor::Lite (
new => 1,
rw => [ qw(foo bar baz) ],
);
package Foo;
sub new {
bless {
foo => undef,
bar => undef,
baz => undef,
}, shift
}
sub foo {
my $self = shift;
$self->{foo} = $_[0] if defined $_[0];
$self->{foo};
}
sub bar {
my $self = shift;
$self->{bar} = $_[0] if defined $_[0];
$self->{bar};
}
sub baz{
my $self = shift;
$self->{baz} = $_[0] if defined $_[0];
$self->{baz};
}
1;
- コンストラクタ/フィールドのアクセサを自動的に定義
- 利用するのに Class::Accessor::Fast を継承する必要があるので使いにくい
-
モダンなオブジェクト指向を実現するモジュール
-
柔軟かつ安全なアクセサの生成
-
型の採用
-
Roleによるインタフェイス指向設計
-
プロジェクトの複雑性をあげるのであまりつかわない
- Moose の軽量版
- Moose はモジュール読み込み時のコストが高い
- 機能は一部制限
- 登場人物を考える = オブジェクト
- 登場人物がそれぞれどのような責務を持つべきかを考える
- 責務にあわせてスコープを限定するように書く
- 「カプセル化で継承でポリモーフィズムが……」とか考えても意味ない
- よりよい、わかりやすく問題をモデリングするための手段
- オブジェクトの利用者、メソッドの呼び出し元との約束
- 責任のないことはやらなくていい
- 責任のないことはやっちゃだめ
- 責務を綺麗に切り分けることで、綺麗に設計できる
- 手作り感あふれるオブジェクト指向
- package に手続きを定義
- bless でデータ(リファレンス)と結びつける
- コンストラクタは自分でつくる
- オブジェクト志向風によびだせるような糖衣
- プログラムを変更する二つの方法 [レガシーコード改善ガイドより]
- 編集して祈る
- テストを書いて保護してから変更する
-
テストがないと、プログラムが正しく動いているかどうかを証明できない
-
大規模プロジェクトでは致命的
- 昔書いたコードは今もうごいているのか?
- 新しいコードと古いコードの整合性はとれているのか?
- 正しい仕様/意図が何だったのかわからなくなっていないか?
-
Perlのような型のない動的言語では特に重要
-
祈らずテストを書こう!
-
正常系
-
異常系
-
境界
-
100% の カバーは難しい
- 命令網羅(C0)/分岐網羅(C1)/条件網羅(C2)
- C2 とかはたいへん
-
必要/危険だと思われるところから書き、少しづつ充実する
- Test::More
- Test::Fatal
- Test::Class
use Test::More;
subtest 'topic' => sub {
use_ok 'Foo::Bar';
isa_ok Foo::Bar->new, 'Foo::Bar';
ok $something_to_be_bool;
is $something_to_be_count, 5;
is_deeply $something_to_be_complicated, {
foo => 'foo',
bar => [qw(bar baz)],
};
};
done_testing;
use Test::Fatal;
ok( exception{ $foo->method }, '例外が発生する');
like( exception { $foo->method }, qr/division by zero/, '0除算エラーが発生する');
isa_ok( exception { $foo->method }, 'Some::Exception::Class', '例外クラスがthrowされる);
- テストコードをメソッドにわける
- xUnit系
package Example::Test;
use parent qw(Test::Class);
use Test::More;
sub setup : Test(setup) {
# 各々のテストが実行される前に実行される
};
sub test_pop : Tests {
ok ...
is ...
is_deeply ...
};
sub teardown : Test(teardown) {
# 各々のテストが実行された後に実行される
};
- テストコードは t ディレクトリに.t拡張子をつけて保存
- t/hoge.t
- proveコマンド(Test::Moreに付属)で実行する
$ prove -lvr t
t/hoge.t ..
ok 1 - L8: is Hoge::hey(10), 100;
1..1
ok
All tests successful.
Files=1, Tests=1, 0 wallclock secs ( 0.02 usr 0.01 sys + 0.03 cusr 0.01 csys = 0.07 CPU)
Result: PASS
- まず、こういう振る舞いで有るべきというテストを書く
is_deeply( [numsort(2, 3, 4, 0, 1)], [0, 1, 2, 3, 4], 'ランダムな数列をsortすると昇順に並ぶ' );
- 次に境界条件での振る舞いを検証するテストを書く
is_deeply( [numsort()], [], '空配列をsortしたら空になる' );
is_deeply( [numsort(100)], [100], '1要素ならそのまま' );
- 例外条件についても確かめる
ok( exception { [numsort('hoge')] },'文字をわたすと例外発生' );
-
リファクタリングとは?
- プログラムの振舞を変えずに実装を変更すること
-
テストがなければ、外部機能の変更がないことを証明できない
- テストがなければリファクタリングではない
-
レガシーなコードに対してはどうする?
- まずは、テストを書ける状態にしよう
-
テストを書いてリファクタリングし、常に綺麗で保守しやすいコードを書きましょう
- perldoc perltoc 便利!
- 定義済み変数
$_ @_ $ @- perldoc perlvars を見るべし
- 定義済み変数
- 正規表現
- perldoc perlre
- 関数
- perldoc -f open
- http://perldoc.jp/ : perldocの日本語訳(バージョンに注意)
- CPANモジュール
- perldoc LWP::UserAgent
- https://metacpan.org/
- perldoc -t (日本語が文字化けしたら)
- はじめてのPerl
- 続・はじめてのPerl
- Perlベストプラクティス
- Perl::Critic
- モダンPerl入門
- App::cpanminus
- CPAN モジュールをインストールする
- Ruby における gem コマンドのようなもの
- cpanm Some::Useful::Module
- global な Perl にインストールされる
- CPAN モジュールをインストールする
- Carton
- プロジェクト毎の依存モジュール管理
- Ruby における Bundler のようなもの
- cpanm Carton すると carton コマンドが入る
- cpanfile にモジュール一覧を書いて carton install
- carton exec -- perl foo.pl と実行するとプロジェクトローカルにインストールしたモジュールが使える
- perldoc cpanfile
- プロジェクト毎の依存モジュール管理
- perl -de0
- Reply
- reply というスクリプトが入る
- プラグイン機構があり .replyrc に色々書いてカスタマイズできる
- Eval::WithLexicals
- tinyreplというスクリプトが入る
- rlwrap と一緒に使うと楽です
- Devel::REPL
- re.plというスクリプトが入る
- Carp::REPL
- Data::Dumper
- YAML
- etc.
use Data::Dumper;
my $foo = {
bar => 'bar',
baz => [qw(hoge fuga)],
};
print Dumper $foo;
# $VAR1 = {
# 'bar' => 'bar',
# 'baz' => [
# 'hoge',
# 'fuga'
# ]
# };
- コードが読まれるものであることを意識する
- あとから誰が読んでもわかりやすく書く
- 暗黙のルールを知る => コードを読みまくる
- テストを書いて意図を伝える
-
Perlプログラミング勘所
-
Perlによるオブジェクト指向プログラミング
-
テストを書こう
-
ヒント
-
今日かいて今日はまろう
- と言っても時間はあまりないので無理せず
皆さんには、今日(2日目)から6日目までの5日の間でIntern-Diaryという日記サービスを作ってもらいます。 コードを書き始める前にこれから作成するソフトウェアの対象について分析し、設計を考えてみましょう。
Intern-Diary でどういうことを実現したいかを改めて考えて箇条書きにしてみましょう。
構築するソフトウェアにはどのような概念が登場するのか考えて分析してみましょう。
以下では Intern-Bookmark を例に考えてみます。
User
ブックマークをするユーザEntry
ブックマークされた記事(URL)Bookmark
ユーザが行ったブックマーク
各クラスがどのような特性を持っているか考えてみましょう。
- User
- ユーザの名前
- Entry
- ブックマークされたURL
- Webサイトのタイトル
- Bookmark
- ブックマークしたUser
- ブックマークしたEntry
- コメント
- 1つのEntryには複数のBookmarkが属する (一対多)
- 1つのUserには複数のBookmarkが属する (一対多)
初日である今日は以下の課題に取り組んでもらいます。 各項目の説明を確認して取り組んでください。
- データモデリング
- モデリングに対応するオブジェクトの実装
- テストに慣れる
- 追加機能について考える(オプション)
Intern-Bookmark のモデリングの講義を参考に簡単な日記システムを考えて、登場する概念(モデル)とその関係を考えてみましょう。 世の中の日記サービス・ブログサービスには様々な機能がありますが、ここでは基本的な機能に絞って考えてもらって構いません。
- 日記を書く人(=ユーザ)は存在しそうですね
- 普通の日記サービスであれば、ユーザごとに個別の日記がありますね
- はてな匿名ダイアリー のようにユーザ個別の日記が存在しない日記サービスもあるにはありますね
- 日記には記事がありますね
講義で説明した「登場する概念(モデル)」と「概念が持つ特性」、「概念間の関係」についてテキストにまとめてください。 また、「概念間の関係」については図も描いてみてください。図については提出する必要はありませんが(含めてもらっても構いません)、メンターにチェックを受けて下さい。
先の課題で考えたデータモデリングに基づくオブジェクトを実装してください。 どのようなデータモデリングを行ったかによって各モデルのできることは微妙に異なりますが、以下のようなことができるようにしてください。
- ユーザーは日記に記事を書くことができる
- 日記は記事の集合を返すことができる
プログラムのインターフェースは自由です。以下に Diary クラスと Entry クラス、 User クラスを用いたサンプルを記しますが、必ずしもこの通りになっている必要はありません。
本日の課題で書いてもらうコードそのものは翌日以降の課程では使いません。
use User;
my $user1 = User->new(name => 'John');
# Diary クラスのインスタンスが返る
my $diary = $user1->add_diary(
name => 'John の日記です',
);
print $diary->name; # John の日記です
# Entry クラスのインスタンスが返る
my $entry1 = $diary->add_entry(
title => '日記だよ',
body => 'これが日記の本文だよ',
);
my $entry2 = $diaryg->add_entry(
title => 'これも日記だよ',
body => 'やっぱり日記の本文だよ',
);
my $recent_entries = $diary->get_recent_entries;
print $recent_entries->[0]->body; # やっぱり日記の本文だよ
「オブジェクトの実装」で実装したクラスの挙動についてのテストを記述してください。 テストの記述については今日の講義の教科書の内容や事前課題のテストなどを参考にしてください。
上記の例であれば、 Diary クラスと Entry クラス、 User クラスのそれぞれについてのテストを書く、ということになります。
必須の課題が終わって時間に余裕がある場合は、上記の課題で作った日記に何らかの機能を追加してみてください。
機能を追加する場合は、追加する機能に対応するテストも実装してください。
追加機能の例を下記にあげます(ここにない機能でもOKです)
- コメント
- ページング
- 購読
- トラックバック
- リブログ
- できるだけテストスクリプトを書く(!!初日は__できるだけ__ではなくて必須です!!)
- 少くとも動かして試してみることができないと、採点できません
- 課題の本質的なところさえ実装すれば、外部モジュールで楽をするのはアリ
- 何が本質なのかを見極めるのも課題のうち
- 余裕があったら機能追加してみましょう
- 講義および教科書から学んだことを課題に反映させよう
- きれいな設計・コードを心がけよう
- 今日のコードは翌日以降の課程では使いませんが、翌日以降は自分の書いたコードに手を入れていくことになります
今日の課題は Intern-Diary リポジトリの basic_diary ディレクトリ以下にファイルを配置してください。 basic_diary ディレクトリの中にも簡単な説明が記載してあるのでそちらを参照してください。