レガシーシステムを理解する方法
結構年季が入ったJavaのレガシーなwebアプリをRailsに移行するというプロジェクトを進めている。レガシーシステムは以下のような状態。
・オリジナルを作ったエンジニアは既に不在
・ビジネスロジックに対するドキュメントは殆ど存在しない
・単体テストは存在しない
・社内WAFで、ドキュメントなし
・複数のシステムが連動している
このような状態は、年季の入ったシステムではわりとよくあることだと思うが、いかんせん「仕様がわからない」ということが色んな所でボトルネックになっていた。その対策方法について、メモしておく。
仕様を理解するための時間を確保する
「その内時間が経てば理解できるよ」という考えもあるだろうが、ある程度年季が入ったシステムをリプレースするなら、初期段階で多少コストをかけてでも、既存仕様を理解するための時間を作った方が効率的だと思う。既存仕様をよく理解しないまま移行開発を行っても、色々な抜け漏れが発生する。また、移行作業とは別に通常開発やテクニカルサポートも行うわけだが、既存システムへの理解が浅いままだと、毎回の調査時間が馬鹿にならない。チームの人数や仕様理解度にもよるが、初期段階で仕様理解の体制を整備してチーム内で共有する時間をとった方が、トータルでみた時間コストが少なくなるというケースは多そうだ。
レガシーコードといえば、「レガシーコード改善ガイド」が有名だが、この本にも以下のように書いてある。
多くの人は、自分にできる最も手近な方法でコードを理解しようとし、その他の方法を使いません。結局のところ、何かを理解しようとするために多くの時間を費やすのは、作業がはかどらず、正しくないやり方に思えるからです。理解する段階をさっさと終わらせることができたら、自分の稼ぎを得るための仕事に取りかかれるはずだと考えるのです。しかし、こうした考えは愚かではないでしょうか。
とはいえ、場当たり的に調査しても効率が悪い。レガシーコード改善ガイドには、レガシーコードを理解するための技法がいくつか解説されているが、自分はその中から以下の3つを好んで使っている。
・仕様化テスト
・ラフスケッチ
・試行リファクタリング
「仕様化テスト」でユーザーから見た仕様を理解する
ユーザーからみたアプリケーションの仕様(一番外側の仕様)を把握する際に効果的。レガシーコード改善ガイドによると、仕様化テストとは以下のようなテストをさす。
仕様化テストは、コードの実際の振る舞いを明らかにするテストです。「システムはこれをするべきだ」とか「こうしていると思う」ということを確認するテストではありません。仕様化テストは、システムの現在の振る舞いをそのまま文書化します。(略)実際のシステムの動きをそのまま文書化したものになります。システムの一部分の動作を理解できると、その知識と、システムに新しく期待する動作に関する知識とを使って、変更を行えるようになります。率直にいって、システムの実際の動作に関する知識はとても重要です。
このやり方の何がよいかというと、仕様理解とテストケース作成の両方が進むということ。(もちろん、テストを正式にやろうとなると、メンテナンスコストが発生するので、実際に運用にのせる際には、注意深く検討する必要がある。)
リプレースプロジェクトなら、単体テストではなく、E2Eテスト
自分のプロジェクトでは「ユーザーからみた仕様は殆ど変えずにリプレースする」という大前提がある。なので、既存のレガシーアプリに単体テストを書いても結局捨て去る事になり、殆ど意味がない。なのでE2Eの仕様化テストを書くようにしている。
ツールとしては、selenium ideが便利。見返しやすいし(slowモードで再生すれば、ああ、こういう動きねというのが分かる)、後々自動テストを整備する際の資産にもなる。もちろん、selenium ideはメンテナンスがちょっと。。というのがあればcapybaraなど使って書くのもあり。この辺は用途やチームの状況に合わせて好きなツールを選択したらよいと思う。
「ラフスケッチ」で主要テーブル・クラスを理解する
コードを読むだけでは混乱する場合、絵を描いたり、メモを取ったりすることが有効です。(略)これらのスケッチには、完全なUMLダイアグラムや、関数呼び出しを表現する特別な記法を使う必要はありません。
上記の通り、形式はラフでよい。テーブル・クラスについて理解を深める時に効果的と思う。ただ、全てのテーブル・クラスを網羅しようとすると大変なので、スケッチの対象は、ビジネス上最も重要なテーブルやクラスのみにする。
ER図もどき
テーブルの関連だけ書いたER図があると、何かと便利。エクセルだとマスター管理が微妙なので、google driveの図形描画を使って、主要なテーブルの関連を書いている(とはいえ、結局主要テーブル以外の関連も調べるケースがあるので、erdというrubyのgemでER図を作るのもあり)
「試行リファクタリング」で細かいロジックを理解する
ロジックが複雑で、かつそのロジックを把握しておかないとまずい部分は、試行リファクタリングを行う。レガシーコード改善ガイドによると、試行リファクタリングとは以下のようなリファクタリングをさす
まず、バージョン管理システムからコードをチェックアウトしてください。テストを書く事は忘れましょう。メソッドの抽出でも、変数の移動でも、そのコードを理解しやすくするために、あらゆる方法を用いてリファクタリングしてください。ただし、そのコードは再びチェックインせずに破棄します。これは試行リファクタリング(scratch refactoring)と呼ばれる手法です
リファクタリングを自由に行って、徐々にロジックへの理解を深めて行く。深掘りするとどこまでもいけるので、理解すべきポイントを把握したらすぐに切り上げるようにする。終わったら別ブランチに保存しておくか、もしくは破棄する。(決して稼働中のアプリに影響を与えないようにする)
まとめ
上記の方針で少しずつレガシーコードの理解を進めていて、結構いい感じにワークしている。なお、Object-Oriented Reengineering Patternsという書籍はこのテーマについて突っ込んで書いているようなので、読んでいるところ。以下から無料のpdfがダウンロードできる
http://scg.unibe.ch/download/oorp/
「Webアプリエンジニア養成読本」を献本してもらった!読んだ!
「Webアプリエンジニア養成読本」を献本してもらいました。あざす!
「点」ではなく「線」
私自身は「28歳の時に未経験からWebエンジニアに転職する」という無謀な試みをして、今年3年目になります。日々開発に勤しむ日々を過ごしておりますが、Webアプリを作るために必要な技術要素って広い!と日々痛感しております。
たまたま自分は本書の著者の1人であるゆーすけべーさんはじめ、先輩諸氏から色々教わることができましたが、教えてくれる人が身近にいて一番よかったのは
何を勉強すればよいのかが分かった
ことです。本書にはその「何を勉強すればよいのか」がコンパクトにまとまっていると思いました。技術書って「点」で書かれているものが多いと思います(PHP、Ruby、MySQLみたいに特定技術ピンポイントで深堀されてる本)。一方、本書は「Webアプリを作るための技術要素」という「線」で書かれているように思いました。
この本をざっと読んで手を動かせば、脳内でWebアプリを作るために必要な技術要素という「点」が繋がって「線」になっていくように思います。Webアプリエンジニア先輩諸氏がconnecting the dotsしてくれた感じで素敵です。
未経験の人は「索引作り」に
まだWebアプリを作ったことが無い方は、きっと「Webアプリを作るために必要な技術的要素の索引」がまだ頭の中に出来上がっていない状態だと思うので、頭の中に索引を作るイメージで読み進めるとよいかと思います。
まずは、この本に書いてあることを、ざっと手を動かしてやってみる(全体を見失わないようにとにかく先に進める)。すると「何を勉強すればよいのか」当たりがつけられるようになる。その後は自分でアプリの企画を考えて実際に作ってみたり、より深く勉強したい技術的要素があればさらに勉強していくみたいな感じで進めるとよいかと。
あ、ただ本書ではクライアントサイドJavaScriptとデータベースについて本当にさらっとしか触れられていないので、その部分はちょい踏み込んで自分で調べる必要が出てくると思います。
2-3年の経験がある人は「穴埋め」に
自分のように2, 3年の経験がある人にとっては、「穴を埋める」という視点で読むとよいかと思いました。もちろん「この本に書かれていることは全て知り尽くしている」という方もいらっしゃるでしょうが、自分は結構穴がありました。例えば、自分は「開発」に携わることが多く「運用」の経験が浅いです。なので、やはりその「運用」を扱っている3章,4章は穴が多かったです。
また、PHPの章はいわゆる「モダン」な開発手法が用いられているので、「レガシーなPHPの保守に手一杯でモダンなPHPの学習が追いついていない」なんて方は結構新しい発見があるのではないでしょうか。具体的にはComposerでライブラリ管理して、WAFとしてSlim(RubyのSinatraライクなWAF)、テンプレートにTwig、ORMにEloquent(Laravelで使われているORM)を使ってサンプルアプリを開発していくという構成になっているので、その辺の経験がなければ新しい発見があるかと。
まとめ
未経験だった頃の自分には無条件でオススメしたい。今の自分にとっても穴が把握できてよかったです!
Backbone.jsでViewからModel・DOMを操作する時の流れ
自分の頭の中の整理のために。
はじめに
Backbone.jsガイドブックを読んでいたら、このような事が書いてあった。
Backbone流MVCでは、ビューとコントローラは両方ともViewが担当します。(略)これらはそれぞれViewのメソッドとして実装しますが、本書では区別のために前者をビューメソッド、後者をコントローラメソッドと呼ぶことにします。(略)重要な点は、DOMイベントに応じてコントローラメソッドが実行されることで処理が始まり、その中でmodelやcollectionを操作し、その結果生じるイベントがビューメソッドを呼ぶ、という流れを意識することです。そのためにも、コントローラメソッドには処理の起点になる以上の仕事を与えず、DOM操作はすべてビューメソッドに集めるよう明確に区別しましょう。
図にするとこんな感じになる(点線がイベントで実線が直操作)
しかし、なぜこのような書き方にするとよいのか?2つの疑問をもった
1. なぜコントローラメソッドとビューメソッドを切り分けるのか?
2. なぜコントローラメソッドがビューメソッドを直接呼び出さないのか?
以降、それぞれ整理してみる
1. なぜコントローラメソッドとビューメソッドを切り分けるのか?
まず、なぜわざわざコントローラメソッドとビューメソッドを切り分ける必要があるのか?逆に以下のように一緒にすると何が困るのか?
思うに、この書き方の問題は、DOMの更新を担当する部分のコードが再利用できないことにあると思う。例えば以下のように複数のイベントハンドラが同じDOM更新を行う場合、上記の書き方だとコードが重複する。
DOM更新の部分を切り出して別メソッドにすれば再利用できる
役割が分離されているので、テストもしやすくなるし、理解もしやすくなる。
2. なぜコントローラメソッドがビューメソッドを直接呼び出さないのか?
では次に、なぜコントローラメソッドが直接ビューメソッドを呼び出さない方がよいのか?つまり以下のように書くと、なぜよくないのか?
恐らく、問題はModelとViewが1対多になった時に起こる。自分が作ったWebサービス(音楽制作のためのWebサービス)でModelとViewが1対多になる部分が多くあったので例にあげる。例えばこんな部分。
この部分では音源を表す1つのModelに対して、再生ボタンを管理するPlayButtonViewと波形を管理するWaveformViewという2つのViewが存在している。例えば音源が停止している状態から再生ボタンと波形それぞれをクリックすると、以下のような動きをする。
・再生ボタンをクリック
- > PlayButtonViewがModelを再生
- > PlayButtonViewが再生ボタンを停止ボタンにする + WaveformViewが波形を動かす
・波形部分をクリック
- > WaveformViewがModelを再生
- > PlayButtonViewが再生ボタンを停止ボタンにする + WaveformViewが波形を動かす
まとめ
もちろん、コントローラとなるViewのメソッドが必ずModelとDOMを更新するわけではない(ただDOMを更新するだけの時や、イベントを発火するだけの時など様々なケースがある)し、ViewとModelが複雑でイベント仲介者(Mediator)を置かないと厳しいこともあるが、「Model更新->DOM更新の基本的ケース」においては、上記のような書き方をするとだいたい幸せになれるように思った。
Backbone.jsをRailsで使った際の、初期設定とルール
こないだまでRailsとBackbone.jsを使ったWEBサービスを作ってました。折角なので、その際の初期設定とちょっとしたルールをまとめておきます。ちなみに、規模感は以下のような感じです。
・ModelとCollection 各約10個
・ViewとTemplate 各約30個
・Routerは使わない(SinglePageApplicationではないので)
バージョンは
・backbone.js: 1.1.0
・rails: 3.2.13
です
ライブラリの配置
依存ライブラリは以下のように配置した
// vendor/assets/javascripts/配下 . |-- backbone/ | |-- backbone-min.js | `-- backbone-min.map |-- json2/ | `-- json2.js |-- underscore/ | |-- underscore-min.js | `-- underscore-min.map ...
ディレクトリ構成
jsコードのディレクトリ構成は以下のようにした。collectionsとmodelsは直下にファイルをおき、viewsとtemplatesはモデルに対してディレクトリを作ってその下にファイルを置いて行った(その構造に合わないファイルも沢山出てきたので、ある程度自由にディレクトリを切っていった)
// app/assets/javascripts/配下 . |-- collections/ | `-- users.js |-- models/ | `-- user.js |-- templates/ | `-- users/ | `-- xxx.hbs |-- views/ | `-- users/ | `-- xxx.js
テンプレート
テンプレートにはhandlebarを使った。rails用のgemでhandlebars_assetsというのがあったので、これを利用
https://github.com/leshill/handlebars_assets
gemをインストール
group :assets do gem 'handlebars_assets' end
テンプレートはこんな感じになる
// app/assets/javascripts/templates/users/user-detail.hbs <span>名前:</span><span>{{name}}</span> <span>メール:</span><span>{{mail}}</span>
テンプレート関数のデフォルト名は「HandlebarsTemplates」だが、気に入らなければ変えられる。例えば「JST」という名前にしたければ、config/initializers/handlebars.rbというファイルを新規作成し、その中身を以下のようにする
HandlebarsAssets::Config.template_namespace = 'JST'
するとViewからはこのようにテンプレートを使えるようになる
// app/assets/javascripts/views/users/user_detail.js MYAPP.UserDetailView = Backbone.View.extend({ template: JST['users/user-detail'], render: function() { this.$el.html(this.template({ name: this.model.get('name'), mail: this.model.get('mail') })); return this; } });
application.js
以上を動かすためのapplication.jsは以下のようになる。require_treeでディレクトリを再起的に読み込むようにする
//= require jquery //= require jquery_ujs //= require json2/json2 //= require underscore/underscore-min //= require backbone/backbone-min //= require handlebars.runtime //= require_tree ./templates //= require application //= require_tree ./models //= require_tree ./collections //= require_tree ./views //= require_tree .
名前空間
名前の衝突を避けるために、アプリケーションに関する名前空間を作る(といってもグローバルなオブジェクトを1つ作るだけ)。application.jsに以下を記載。
window.MYAPP = window.MYAPP || {};
クラス名規則
上記の名前空間の下にクラスとなるオブジェクトを作っていくのだが、その際のクラス名は以下のようにした。全て大文字ではじめる。CollectionはModelの複数形。ViewはModel/Collectionと1対1であれば頭にModel名をつける(そのような関係にならない場合も多いので、その場合は自由に命名する)。
// Model MYAPP.User = Backbone.Model.extend({ /* ... */ }); // Collection MYAPP.Users = Backbone.Collection.extend({ /* ... */ }); // View MYAPP.UserListView = Backbone.View.extend({ /* ... */ });
アプリケーション全体での共通前処理
アプリケーション全体で必ず実行したい共通の前処理が存在したので、Backboneオブジェクト自体のイベントを利用した。application.jsに共通前処理を書き、それが終わったらBackbone.triggerでカスタムイベント(以下の例ではinit)を発火する。各ページのjavascriptではこのイベントを購読しておくという形。
application.js
$(function() { // 共通前処理 // ... Backbone.trigger('init'); });
各ページ
Backbone.on('init', function() { // 各ページの処理 });
サーバーサイドで生成するjsonのハンドリング
サーバーサイドで生成するjsonのハンドリングは、基本的にはas_jsonメソッドを用いた。使い方はこちらのブログが詳しい
http://d.hatena.ne.jp/gutskun/20130409/1365518684
もっと細かいハンドリングをしたい場合にはgemを使った方がよいと思う。Rails4からはデフォルトで組み込まれているJbuilderかRABLあたりが有名
http://railscasts.com/episodes/320-jbuilder?language=ja&view=asciicast
http://railscasts.com/episodes/322-rabl?language=ja&view=asciicast
初期データ投入
大きく2つの方法があるように思う。1つはデータが空のオブジェクトを作ってからサーバーにfetchするもの。もう1つはhtmlレンダリング時にデータも入れてしまうもの。
1つ目の方法
<div id="user-list-container"></div> <%= javascript_tag do %> Backbone.on('init', function() { var users = new MYAPP.Users(); var userListView = new MYAPP.UserListView({collection: users}); $('#user-list-container').html(userListView.render().el); users.fetch({reset: true}); }); <% end %>
この場合の流れは
・データが空の状態でViewレンダリング
・ model/collectionがサーバーへデータをfetch
・ model/collectionのresetイベント発火
・ viewがresetイベントキャッチして、再度レンダリング
2つ目の方法
<div id="user-list-container"></div> <%= javascript_tag do %> Backbone.on('init', function() { var users = new MYAPP.Users(JSON.parse('<%= j @users.to_json.html_safe %>')); var userListView = new MYAPP.UserListView({collection: users}); $('#user-list-container').html(userListView.render().el); }); <% end %>
この場合の流れは
・モデルのjsonデータをサーバー側でjsコードとして出力
・データがある状態でViewがレンダリング
2つ目の方法の方がレンダリングが速いので、特に理由がなければ2つ目の方法を使う。
クライアントサイドでレンダリングするか、サーバーサイドでレンダリングするか
前述コードは、backbone管理下の要素をクライアントサイドで生成するようなコードだった。けれど、そうするとその部分の要素に関してはSEOが死ぬ。この問題は別にbackboneに限った話ではなく、angularのような他のライブラリを使っていても、クライアントサイドでレンダリングする限り起こる問題。対策法はこちらのサイトなどが詳しい
http://info.appdirect.com/blog/solving-the-javascript-seo-conundrum-part-one
http://www.ng-newsletter.com/posts/serious-angular-seo.html
けれど、今回はそこまで大掛かりなことをせずに局所的に対策すればよい程度だったので、SEO対策な必要なページはサーバーサイドでレンダリングし、クライアントサイドではサーバーサイドでレンダリングされたhtmlに対してbackbone.jsのオブジェクトをアタッチするようにした。参考にしたのはこちらのstackoverflowのポスト
http://stackoverflow.com/questions/7549306/single-page-js-websites-and-seo
コードはこんな感じ。
<div id="user-list-container"> <% @users.each do |user| %> <div class="user-detail" data-id="<%= user.id %>"> <span>名前:</span><span><%= user.name %></span> <span>メール:</span><span><%= user.mail %></span> </div> <% end %> </div> <%= javascript_tag do %> Backbone.on('init', function() { var users = new MYAPP.Users(JSON.parse('<%= j @users.to_json.html_safe %>')); var userListView = new MYAPP.UserListView({el: $('#user-list-container'), collection: users}); userListView.$('div.user-detail').each(function(i, el) { var el = $(el); var id = el.attr('data-id'); var user = users.get(id); new MYAPP.UserDetailView({el: el, model: user}); }); }); <% end %>
このアプローチだと、テンプレートをクライアント側でも使いたい場合にコードの重複が発生するが、今回はそのような箇所がそれほど多くなかったので、よしとした。
参考になったソース
https://github.com/documentcloud/documentcloud
Backbone.jsの本家本元、documentcloud。そのソースコードがGithubで公開されてる。サーバーサイドはRails。ちょっとバージョンが古いので、書き方もレガシーなメソッドを使っていたりするけど、Todoアプリみたいなサンプルとは違って実運用されている大きなアプリケーションなので多いに参考になった。
https://github.com/samuelclay/NewsBlur
SinglePageApplicationのRSSリーダーNewsBlur。こちらもdocumentcloudのメンバーが作っているっぽい。サーバーサイドはDjango。 media/js/newsblur/配下にbackboneを使ったjsコードが沢山ある。
終わり
いわゆるSinglePageApplicationといったような、ほとんど画面遷移をしないアプリケーションにする場合、またちょっとポイントが変わってくるとは思います(Routerの役割が大きくなるので)。けれどそこまでいかないアプリケーションであれば、上記のような構成+ルールで破綻せずにいけそうな感じでしたー
RubyのIntegerを拡張してFizzBuzzを問題をやってみた
FizzBuzz問題というのをやったことが無かったので、Rubyでやってみた。「ある整数がある整数で割り切れるか?」という部分を抽象化すると、少し汎用的になってよいかと思った。
class Integer def dividable?(n) self % n == 0 end end (1..100).each do |n| puts n.dividable?(15) ? 'FizzBuzz' : n.dividable?(5) ? 'Buzz' : n.dividable?(3) ? 'Fizz' : n end
※ Rubyだと0はtrueになるので、== 0の結果を返してあげる
ネーミングに困ったとき、RailsのAPIが参考になった
Perlいつやるの?今でしょ!
ゆーすけべー先生のPerl入門書がKindle本で出ました。詳細はこちら
僕はRuby, PHP, JavaScriptしかまともに触った事が無かったのですが、
心の中からこんな声が聞こえました↓
はいぃぃぃぃ!ということで、Perl入門してみることにしましたです。ちなみにゆーすけべー先生のKindle本には、Perlの基礎文法〜Webアプリの作り方まで、結構広範囲についてサクッと書かれていたのですが、各章の「キリ」がよくて、つまみ食いしながら読み進めることができる感じ。なので自分はまず基礎文法だけやってみたです(細かいところはすっとばして、頻出しそうなところだけつまみ食い)。
※ 以下のコードはKindle本に出てくるサンプルコードとは全く異なり、自分で書いたコードですー
※ ちなみに自分はKindle持ってないので、iphoneのKindleアプリ使って読みますた
配列、ハッシュ
配列、ハッシュの扱い方は色々な方法があるようだけど、自分は以下のように無名リファレンスを使う方法が使いやすかった。javascriptの配列リテラルやオブジェクトリテラルに似ている!
use strict; use warnings; my $users = [ {id => '001', name => 'hoge', sex => 'man'}, {id => '002', name => 'fuge', sex => 'woman'} ]; # 要素にアクセス print $users->[0]->{name}; #hoge # ループでまわす foreach my $user (@$users) { print $user->{name}; #hogefuge }
関数
javascriptのように無名関数を引数に渡して、コールバックできるんすね!引数の扱い方がちょっと独特だけど、そのうち慣れるでしょー
use strict; use warnings; sub foo { my $callback = shift; my $y = 'yyy'; $callback->($y); } # レキシカル変数 my $x = 'xxx'; # 無名関数を関数に渡す foo(sub { my $yy = shift; print "$x\n"; # xxx print "$yy\n"; # yyy });
cpan
cpanはRubyのgemみたいなもんかな。Kindle本でも紹介されてたAcme::Oppaiというジョークモジュールを使ってみた。Acme::ってのはジョークモジュールにつける名前空間らしい。::で名前空間を繋げて行くあたりはRubyに似てる。ちなみにperlとcpanmの環境設定は、Kindle本の「一歩進んだPerlの環境つくり」という章に書いてあったー
$ cpanm Acme::Oppai
use strict; use warnings; use Acme::Oppai; print Acme::Oppai->oppai; # _ ∩ #( ゜∀゜)彡 おっぱい!おっぱい! # ⊂彡
OOP
OOPの勉強がてらAcme::Oppaiの拡張的なものを作ってみた。コマンドラインからoppaiコマンドを叩くと、引数によって表示されるAAが変わる代物。
ディレクトリ構成
. ├── Acme/ │ ├── OppaiEx/ │ │ ├── KnightsOfRound.pm │ │ ├── Kuma.pm │ │ └── Manager.pm │ └── OppaiEx.pm └── oppai*
oppai
コマンドとなるプログラム。こいつに実行権限をつけて、パスを通す
#!/usr/bin/env perl use strict; use warnings; use Acme::Oppai; use Acme::OppaiEx; my $key = @ARGV ? shift : ''; my $oppai; if ($key eq "kuma") { $oppai = Acme::OppaiEx::Kuma->new(); } elsif ($key eq "knights") { $oppai = Acme::OppaiEx::KnightsOfRound->new(); } else { $oppai = Acme::Oppai->new(); } my $manager = Acme::OppaiEx::Manager->new($oppai); $manager->show;
Acme/OppaiEx.pm
各パッケージを読み込むプログラム
package Acme::OppaiEx; use strict; use warnings; use Acme::OppaiEx::Manager; use Acme::OppaiEx::Kuma; use Acme::OppaiEx::KnightsOfRound; 1;
Acme/OppaiEx/Manager.pm
oppaiオブジェクトを管理するパッケージ。oppaiプロパティにoppaiオブジェクトをセットし、showメソッドでそのオブジェクトのoppaiメソッドを呼び出す。
package Acme::OppaiEx::Manager; use strict; use warnings; sub new { my($class, $oppai) = @_; bless { oppai => $oppai }, $class; } sub show { my $self = shift; print $self->{oppai}->oppai; } 1;
Acme/OppaiEx/Kuma.pm
クマおっぱいを返すパッケージ
package Acme::OppaiEx::Kuma; use strict; use warnings; sub new { bless {}; } sub oppai { <<'EOS'; ∩___∩ | ノ ヽ (⌒ヽ /⌒) ● ● | / / ミ / / ( _●_) ミ / おっぱい!おっぱい! .( ヽ |∪| ` 彡 \ _ヽノ_ 彡 / (__ ___/ | / | /\ \ | / ) ) ∪ ( \ \_) EOS } 1;
Acme/OppaiEx/KnightsOfRound.pm
円卓の騎士的なおっぱいを返すパッケージ
package Acme::OppaiEx::KnightsOfRound; use strict; use warnings; sub new { bless {}; } sub oppai { <<'EOS'; \\ // \\ お っ ぱ い. // \\ // \\ お っ ぱ い // _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡 _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. | | | | | | | | | | | | | | し ⌒J. し ⌒J. し ⌒J. し ⌒J. し ⌒J. し ⌒J. し ⌒J EOS } 1;
実行!
$ oppai _ ∩ ( ゜∀゜)彡 おっぱい!おっぱい! ⊂彡
$ oppai kuma ∩___∩ | ノ ヽ (⌒ヽ /⌒) ● ● | / / ミ / / ( _●_) ミ / おっぱい!おっぱい! .( ヽ |∪| ` 彡 \ _ヽノ_ 彡 / (__ ___/ | / | /\ \ | / ) ) ∪ ( \ \_)
$ oppai knights \\ // \\ お っ ぱ い. // \\ // \\ お っ ぱ い // _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡 _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. _ _∩. ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ゚∀゚)彡 ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. ( ⊂彡. | | | | | | | | | | | | | | し ⌒J. し ⌒J. し ⌒J. し ⌒J. し ⌒J. し ⌒J. し ⌒J
以上
とりあえず基礎文法はざっと入門した!こんどはWebアプリの作り方に入門してみよー
参考サイト
細かい文法とかは以下のサイトが参考になりましたー
基礎文法
http://d.hatena.ne.jp/perlcodesample/20091226/1264257759
配列、ハッシュ
http://d.hatena.ne.jp/perlcodesample/20100930/1278596435
関数
http://d.hatena.ne.jp/marsonic/20090302/1235984068
無名関数、クロージャ
http://d.hatena.ne.jp/bingo_nakanishi_perl/20090211/1234317567
OOP
http://d.hatena.ne.jp/perlcodesample/20090317/1237125522