2013 年 9月 の投稿一覧

toda

システムの規模の肥大化に伴うクラス数増大により、あるクラスにメソッド追加が集中することがあります。基盤となるクラスのメソッドが増大していくものです。
クラスを追加するときに、その関連メソッドを基盤クラス側のメソッドとして追加していくとそのような事態に陥りがちです。いわゆる、Blob(肥満児)アンチパターンですね。

あまり上手くない例えですが、ある会員情報を保存している MemberTable クラスに対して、その会員情報を操作するフォームを表す Form クラスが追加されていることがある (1:N) とします。

その MemberTable に所属している Form の一覧を取得する getForms() メソッドを MemberTable クラスに追加したいわけですが、静的にメソッドを追加した場合、Form クラスが使われない場合は無駄なメソッドが追加されることになります。

Form クラスだけならまだしも、このようなクラスが増えていくと、常に使用されるとは限らないメソッドが MemberTable クラスに追加されていくことになり、MemberTableのコードが無駄に肥大化することになります。また、 Form クラスの修正の影響が MemberTable クラスに及ぶことがありえるため、修正すべき箇所が複数のコードに分散してしまい保守性の観点からも望ましくはありません。

この問題の一つの解決手段として、メソッドを(必要なときに)動的に追加することで、静的に定義されるメソッド数を減らすという方法があります。
Form クラスが読み込まれた時に、 Form クラスのコード側で MemberTable クラスに getForms() メソッドを追加することができれば、MemberTable クラスのソースに手を加える必要はないので、MemberTable の肥大化を防ぐことができますし、 Form クラスの修正を Form クラスのコードに抑えることができます。

具体的な実装例としては、動的に追加するメソッドを無名関数として登録しておき、基盤クラスの __call() マジックメソッドを経由して呼び出すという方法があります。

class MemberTable {
  // 動的に追加されたメソッドの配列
  static private $_methods = array();

  // 動的にメソッドを追加
  static public function addMethod($name, $func) {
    self::$_methods[$name] = $func;
  }

  // 動的に追加されたメソッドの呼び出し
  public function __call($name, $args)
  {
    $func = self::$_methods[$name];

    // 無名関数に $this を bind してから実行
    call_user_func_array($func->bindTo($this, 'MemberTable'), $args);
  }
}

Form クラス側のコードでは、読み込み時に MemberTable::addMethod() でメソッドを追加します。

class Form {
  // 詳細略
}

// MemberTable クラスに getForms() メソッドを追加
MemberTable::addMethod("getForms", function(){
  // 詳細略
  return $forms;
});

Form クラスが読み込まれるときに、MemberTable::addMethod() でメソッドを追加されているので、何も気にせずに getForms() を呼び出すことができます。

$forms = (new MemberTable())->getForms();

ポイントはメソッドの呼び出し時に、bindTo() で無名関数の $this を bind しておくことです。これを忘れると、無名関数内で $this を参照できません。
あと、bindTo() するときにスコープ(第2引数)にクラス名を指定しておく必要があることをお忘れなく。

しかし、bindTo() が使えるのは PHP 5.4 以降であって、公式パッケージが未だに PHP 5.3 である RedHat/CentOS では、無名関数を $this に bind することはできません。

そこで無名関数の呼び出し時に、引数に $this を暗黙的に追加して、無名関数側の引数として受け取るという方法があります。

class MemberTable {
  public function __call($name, $args)
  {
    // 引数の先頭に $this を追加
    array_unshift($args, $this);

    return call_user_func_array(self::$this->_methods[$name], $args);
  }
}

無名関数を登録するときは、最初の引数で $this を受け取るようにします。
クラスのメソッドは $self 経由で呼び出します。

class::addMethod('method', funciton($self, $arg1, $arg2..){
  // 詳細略
  return $forms;

  // メソッドを呼び出すときは $self から呼び出す
  $self->someMethod();
});

これである程度同様のことが実現できます。
もちろん制約もあって、$self は自身の private, protected メソッドを呼び出すことはできません。

早く RedHat/CentOS の公式パッケージが PHP 5.4 ベースになってくれることを期待したいところです。

Tags:

s-24

9月も早いものですね!2013年も終わりそうで寂しいs-24です。
7年ぶりに草野球を欠席してしまいました。皆さんも体調管理に気を付けて下さい。

前回記事 では PHPMD の使い方を簡単にご紹介しましたが、今回は続編ということで PHPMD と Jenkins を組み合わせて typo などによる未定義変数の検出を自動化しちゃいます。設定方法と、普段どう使ってるかを紹介しますね!

目次

  1. プラグインのインストール
  2. プロジェクトの作成と設定
  3. 普段の運用

プラグインのインストール

以下では Jenkins のセットアップ自体は完了している前提で話を進めます。

まず、プラグインマネージャーから以下の 2 つのプラグインをインストールします。「Jenkinsの管理」→「プラグインの管理」をクリックすると、プラグイン管理画面に移りますので、そちらからインストールできます。(アップデートがあれば、アップデートありますって表示が出てくれますよ!)

  • Phing Plugin
  • PMD Plugin

01

 

プロジェクトの作成と設定

さて、それでは新規ジョブを作成します。「新規ジョブ作成」を選択し、ジョブ名の入力とプロジェクトの種類の選択を行い、「OK」をクリックします。今回はジョブ名を Product-PHPMD-UnUsedCodeCheck としてみます。

01

次に設定画面が出てくるので、各項目設定していきます。

  1. 「説明」にプロジェクトの概要を記載します。
  2. 01

  3. 「ソースコード管理システム」でソースコードのリポジトリ置き場を指定します。
  4. 01

  5. 日次で実行する場合は、「ビルド・トリガ」の項目で「定期的に実行」をクリックして、スケジュールに設定を記載します。ここでは早朝 4 時 10 分にセットしています。書式に関しては右側のクエスチョンマークをクリックするとヘルプが出ますのでそちらを参照ください。
  6. 01

  7. 「ビルド」の「ビルド手順の追加▼」をクリックして追加する処理の一覧を表示し、その中から「Phing の呼び出し」を選択します。「使用するPhing」、「ターゲット」については空欄でOKです。
  8. 01

    01

  9. 最後に、ビルド後の処理に PHPMD を追加します。「ビルド後の処理」の「ビルド後の処理の追加▼」をクリックして追加する処理の一覧を表示し、その中から「PMD警告の集計」を選択します。「集計するファイル」については空欄でOKです。
  10. 01

    01

ここまで設定が完了したら「保存」をクリックして設定を保存します。

さて、次はビルドファイルの作成と設置ですが、置き場であるワークスペースを作成する必要があるので、「ビルド実行」をクリックして一度ビルドを実行します。

01

この時点でのビルドは失敗しますが OK です。

01

次はビルドファイルの作成と設置です。こちらは Jenkins を実行しているサーバにログインして作業を行います。

Jenkins のプロジェクト関連ファイルの置き場は /var/lib/jenkins/jobs/ とします。

上記のビルドで /var/lib/jenkins/jobs/<プロジェクト名>/workspace/ というディレクトリが作成されているはずなので、そのディレクトリに以下の内容のファイルを build.xml として設置して下さい。なお、①「project name=」に記載するプロジェクト名、②「fileset dir=」に設定するディレクトリパス、③「include name=」、「exclude name=」に記載するチェック対象プログラム、検査から除外するプログラムについては適宜変更して下さい。

ちなみに今回のルールセットは unusedcode のみにしています。他にも codesize、design、naming なども指定できますが (詳しくは 前回記事 参照) 、今回の目的では unusedcode で十分なので敢えてそれだけにしています。

<?xml version="1.0" encoding="utf-8" ?>
<project name= "Product-PHPMD-UnUsedCodeCheck" basedir= "." default= "main">

    <target name="main" depends="phpmd"></target>

    <!-- PHPMD -->
    <target name="phpmd" >
         <phpmd rulesets= "unusedcode" >
             <fileset dir= "/var/lib/jenkins/jobs/Product-PHPMD-UnUsedCodeCheck/workspace/include/product/" >
                 <include name= "**/*.php" />
                 <exclude name= "**/*Test.php" />
             </fileset>
             <formatter type= "xml" outfile= "reports/pmd.xml"/>
         </phpmd>
    </target>
</project>

上記の build.xml ではレポートファイルの置き場所を reports ディレクトリとしているため、そのディレクトリを作成しておく必要があります。

$ mkdir /var/lib/jenkins/jobs/Product-PHPMD-UnUsedCodeCheck/workspace/reports/

設定は以上です。では、改めてビルドを実行してみましょう。

01

ビルド履歴にビルドの進捗がゲージで表示されます。このゲージをクリックするとビルド中なら進捗状況が、ビルド後 (失敗) であればどこで止まったかを参照できます。

01

実行後、ビルド履歴のビルド日時をクリックすると以下のように表示されます。これで未使用コードの一覧を参照することができます。リンクを手繰るとどのファイルにどの程度の未使用コードがあるか、実際にコードのどの部分かを追っていくことができます (その画面は後述の「普段の運用」の部分でお見せしますね)。

01

このように 1 度目のビルドでは未使用のコードについて網羅的に検出を行ってくれます。その後のビルドでは、前回ビルドとの差 (新しく出てきたもの、修正されたもの) についての情報も表示してくれます。

01

 

普段の運用

私の場合、1 度目の結果をざっと確認した後の 2 度目以降の確認では新しい警告についてのみ確認を行い、未定義変数などが無いかをチェックしています。今の運用では未使用コードをゼロにすることが目的ではなく、変更が加わった部分に未定義変数など問題があるコードがないかを早期に発見することが目的なので、毎日のチェックの流れは以下のようにしています。

  1. 早朝に Jenkins 側で自動ビルドとレポートの作成 (上記設定で自動化)
  2. 出社後にビルド結果のレポートを確認 (主に新しい警告の有無)
  3. 01

  4. 新しい警告が出ている場合はコードをチェック
    • 上記画面の「XXX 個の新しい警告」をクリックすると、警告が生じたプログラムの一覧が表示されます。
    • 01

    • 更にそのプログラムをクリックすると、実際に未使用コードが検出された場所をハイライト表示してくれます。
    • 01

  5. 未定義変数など問題があるコードを見つけたら開発チームに共有

こんな感じで日々のコードチェックを効率化しています。だいぶいい感じでチェックできてますが、もっと効率が良い方法あれば是非教えて下さい!

参考サイトやオススメ本

参考サイト: PHPMD(PHP Mess Detector)をjenkinsで利用してみる

オススメ本: 「Jenkins実践入門 ~ビルド・テスト・デプロイを自動化する技術」

Tags: , , ,