著者アーカイブ 杉浦

著者:杉浦

凝集度とLCOMと抜け道

 コーディングでクラス分けをする際、クラスはランダムに分けるのでなく何かを基準にして分けるべきです。自分の場合、コーディング規約(明文化されていないフレームワークのおすすめ方法とか含めて)に沿うことを前提として役割、想定する実体あたりを基準にしています。
 何を基準にして分けるにしろ適切に分けられたならば、分けられ作られたプロパティ、メソッドはまとまっており、関連性が出てきます。凝集度とはこの関連性の度合いのことであり、LCOM(Lack of Cohesion in Methods)は具体的に凝集度を数値化した時の指標の一つです。LCOMは次の式で表されます。
 
 前半のAほにゃららはメソッドがアクセスするプロパティ数の平均になります。そのためこの部分の値は0からMをとります。またMのみが極大の時、Aのみが極大の時にLCOM=1になります。
 要はLCOMが0に近づくほどプロパティの数に対してプロパティにアクセスするメソッドの数が多く、LCOMの値が大きくなるほどプロパティを無視しているメソッドが多い、ということです。LCOMは 1 – (sum(a) / (M * A))や(M * A) / sum(a)などいくらか変形がありますが、プロパティとメソッドの数とプロパティにアクセスするメソッドの数の比率から求められる値であり、小さいほど良い値であることは共通して作られています。
 LCOMが真価を発揮するのはクラスの使い方が、あるオブジェクトの情報を持ち、そのオブジェクトに対しての操作メソッドをまとめる、という時です。この時LCOMが小さい程そのクラスは一つのオブジェクトに関する情報と動作の操作に専念しているといえます。とても凝集しているわけですね。
 LCOMはいい指標ですが適切な設計であっても悪い値になる時があります。それは疎な情報を持つオブジェクトを扱う時です。例えばデータベース中のテーブルのカラムを全てオブジェクトのプロパティに取り込んだ時、プログラムで触らないカラムが現れることがあります。そういった時、オブジェクトを正確に反映したクラスであってもLCOMの値は悪くなります。
 またLCOMの値を良くすることを目的そのものとしてはいけません。LCOMは凝集度を評価するための値であり、凝集度もクラスの中のプロパティ、メソッドのまとまり具合の度合い、結局はクラスの良さそのものではありません。極端な話、メソッドで参照するのみの意味のないプロパティを大量に持たせ、都度呼び出せばLCOMは良い値にできてしまいます。その様なクラスが良いクラスなわけがありません。LCOMはコードの評価の一側面として扱うのがいいでしょう。

著者:杉浦

【PHP】コードの複雑さを調べるPHP静的解析ツールPhpMetricsの紹介

 ソースコードを静的に(実行せずに)評価する際には様々な指標があります。大体、条件文の多さ、行数、クラスの数、依存関係、抽象度、意味のあるコメントの量あたりを元にして色々計算するのですが、それらすべてを把握するのは困難です。PhpMetricsはそういった指標を計算してレポートを出力してくれる静的解析ツールです。
PhpMetrics
 Composerで提供されているのでインストールは

composer global require phpmetrics/phpmetrics

で十分です。使い方もお手軽です。例えばHTML形式のレポートが欲しいなら

phpmetrics --report-html=[htmlレポートの出力先] --exclude=[調査しないディレクトリA],[調査しないディレクトリB] [調査対象のソースコードのディレクトリ] 

 これだけです。フレームワークやライブラリを用いている場合、[調査しないディレクトリ]を指定しないとそれらを、神クラス――機能が集中しすぎているクラス――扱いして結構な数の警告が出ます。
 実行結果のHTMLを見ると次の様な画面が出ます。

 PhpMetrics report
 とりあえず特に気にするべきなのが赤丸で囲ったViolationsとAverage cyclomatic complexity by classとMaintainability / complexityです。
 Violationsはバグりそう、読み難そうといった原因がはっきりしているコードをその理由付きでリスト化しています。ここに現れたものを解決するだけでいくらかマシになります。

 Average cyclomatic complexity by classはクラス毎の循環複雑度の平均です。この値が大きいと条件文などによる分岐が多いコードということです。分岐が多いと処理を追う際の組み合わせが膨大になり、テストも困難になってしまいます。
 Maintainability / complexityはソースコード中の各クラス、ファイルを図示したものです。色は危険度、大きさはコード量を表しています。この図が小さい緑丸で染まったコードが分かりやすいコードということです。

著者:杉浦

【PhpStorm】インターフェイスとPhpStormで一貫性のあるコードを量産する

 インターフェイスはあるクラスが実装するべきメソッドを定義する仕組みです。あるクラスにおいてインターフェイスを用いると宣言したにも関わらず、インターフェイス内で定義したメソッドが足りないならばエラーになります。
PHP: オブジェクト インターフェイス – Manual
 例えば次のインターフェイスならば

use Illuminate\Database\Eloquent\Builder;

interface Searchable
{
    public function scopeSelectForSearch(Builder $query): Builder;
    public function scopeWhereForSearch(Builder $query, array $search_criteria): Builder;
    public function scopeSortForSearch(Builder $query, array $sort_option): Builder;
}

 次の様に実装を強要できます。

use Illuminate\Database\Eloquent\Builder;

class Hoge implements Searchable
{
    public function scopeSelectForSearch(Builder $query): Builder
    {
        // 検索用select文クエリ構築
        return $query;
    }

    public function scopeWhereForSearch(Builder $query, array $search_criteria): Builder
    {
        // 検索用where文クエリ構築
        return $query;
    }

    public function scopeSortForSearch(Builder $query, array $sort_option): Builder
    {
        // 検索用order byt文クエリ構築
        return $query;
    }
}

 インターフェイスはコーディングの自由度を下げる仕組みです。一定の制約を設けることによって一貫性を持ったコーディングを実現します。例のSearchableインターフェイスを実装したクラスならば、必ずBuilderクラスないしBuilderクラスを継承したクラスのインスタンスを引数にするscopeSelectForSearchメソッドが存在し、そのscopeSelectForSearchメソッドからはBuilderクラスないしBuilderクラスを継承したクラスのインスタンスが返ってくる、と保障されます。一人で小さいプログラムを組んでいるときには開発速度の障害であり無縁な方が良い位の仕組みですが、相手の頭の中を簡単に知れない多人数開発であったり記憶が飛ぶ様な大規模開発では、インターフェイスによるクラス設計がソースコードの破綻を防ぎます。

 インターフェイスは便利な仕組みですが、インターフェイスで定義された決まりきった構文をいちいち書いたりコピペして直したりするのは面倒です。PhpStormにはこれを自動で記述する仕組みが備わっています。
 
 Alt+Insert→メソッドの実装→実装するメソッドを選択→OK、で宣言がずらっと並びます。便利です。PhpStormのAlt+Insertからは他にも決まりきった文字列を色々出力できます。例はPHPですがJavaScriptにも対応しています。

著者:杉浦

【Laravel】DBに依存させずにEloquentモデルをテストする

 EloquentはLaravelに備わっているORM(Object-relational mapping)です。
Eloquent:利用の開始 5.7 Laravel
 オブジェクト関連マッピングとはオブジェクトとDB(データベース)の関連のマッピングのことであり、その都合上オブジェクトはデータベースと密接な関係にあります。

<?php
class Post extends Model
{
    // これだけでpostsテーブル中の各レコードを呼べる。
}


 この密接な関係のため、モデルをテストする際にDBが付きまといがちです。何を呼び出すにしても、DBの中を見に行きます。しかしモデルのメソッドのテストはDBから切り離されるべきです。これはモデルのテストがそれ以上掘り下げるべき最小単位のユニットテストであり、テスト失敗の原因がテストやソースコードのエラーかDBのエラーか考える手間を省くべきでもあるからです。
 次の様なコードを記述することでDBからEloquentモデルを切り離してテストできます。

<?php

namespace Tests\Unit\App\Models;

use App\Models\User;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection;
use Tests\TestCase;

class UserTest extends TestCase
{
    public function testScopeFindOrFailPort()
    {
        self::assertInstanceOf(User::class, UserMock::scopeFindOrFailPort(UserMock::query(), '20100'));
        try {
            UserMock::scopeFindOrFailPort(UserMock::query(), 'hoge');
            self::assertTrue(false, 'not foundでなかった');
        } catch (ModelNotFoundException $e) {
            self::assertTrue(true);
        }
    }
}

/**
 * モデルUserのモック。データベースに関わらず一定のものを返すようにしている。
 */
class UserMock extends User
{
    public $mock_data;
    public $mock_return_data;

    /**
     * {@inheritdoc}
     * UserMock constructor.
     * @param array $attributes
     */
    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
// テストデータ定義
        $mocks = [];
        for ($i = 0; $i < 5; ++$i) {
            $mocks[] = (new User())->forceFill([
                'id' => $i,
                'group_key' => $i % 3,
                'hash' => 'hash_'.$i,
                'port' => ['1234', '12345', '20100'][$i % 2],
                'created_at' => now(),
                'updated_at' => now(),
                'deleted_at' => null,
            ]);
        }
        $this->mock_data = new Collection($mocks);
    }

// DBにアクセスするLaravel内部に隠れている部分を引っ張り出して置き換え

    /**
     * {@inheritdoc}
     * @return User|Builder|UserMock
     */
    public static function query()
    {
        return new self();
    }

    /**
     * {@inheritdoc}
     * @param  array                    $columns
     * @return User|Model|object|null
     */
    /** @noinspection PhpHierarchyChecksInspection */
    public function first($columns = [])
    {
        return $this->mock_data->first();
    }

    /**
     * {@inheritdoc}
     * @param  array|Closure|string $column
     * @param  string|null          $operator
     * @param  null                 $value
     * @param  string               $boolean
     * @return User|UserMock
     */
    /** @noinspection PhpHierarchyChecksInspection */
    public function where($column, $operator = null, $value = null, $boolean = 'and')
    {
        if (func_num_args() === 2) {
            $value = $operator;

            $operator = '=';
        }
        $this->mock_data = $this->mock_data->where($column, $operator, $value);

        return $this;
    }
}

 重要なのは、テスト対象のモデルを継承しているがテスト対象のモデル内で定義されたメソッドを変更していないクラス、をテストコードに記述することです。このUserMockクラスは、UserクラスのDBにアクセスしてデータを取ってくる部分を、テストコード中で定義する値を取ってくる様に上書きしたクラスです。テスト対象そのものを置き換えている様にも見えますが、この置き換えている部分は大過ないことが担保されているLaravel内部のテスト済みコードのModelクラスの部分です。

 テスト用クラスUserMockはテスト対象クラスUserで定義されたコードを変更せず、Userの更に奥のModelから継承したコードを変更しています。これによりUserクラスを破壊することなく、DBのアクセスの代わりにローカル変数へのアクセスを行うUserクラスであるUserMockクラスが作れます。
 このようにするとデータベースに依存することなくEloquentのテストが出来ます。こういったコードはローカルのPHPのみで完結しており、実行速度が速い、他のデータリソースを汚染しない、と便利です。ここまで小さいテストならばモデル全体を通しても10秒とかからずにテストが終わります。おかげでGitにコミットする度にローカルで走らせても苦になりません。

DBへのアクセスメソッドは大体\Illuminate\Database\Eloquent\Builder以下にそろっています。

著者:杉浦

【PhpStorm】PhpStorm上でコードの一部分のみGitを辿る

 PhpStormはGitを上手く扱えるIDEです。単にGitの操作と木を見るのみでなく、フックやテンプレートも用意しています。


【PhpStorm】PhpStormでpre-commitのGitフック
 PhpStormはGitを読みやすくする機能も備えています。その一つがコードの一部分のみのGit Historyの表示です。操作はコードを選択して右クリック、Git→選択のヒストリーを表示、とするだけです。

 すると次の様に選択部の変更のみのGit Historyが見えます。

 この機能を使うとGitの中をひーこら検索する必要がずいぶん減ります。特に歴史あるリポジトリの一部分を追う時、過去の変更に差し戻す時に活躍します。

著者:杉浦

【Python】PythonでSlackの通知を送る

 Slack開発元謹製のIncoming Webhookとライブラリslackwebを繋げることでとても楽にSlackへ通知を送ることが出来ます。
 どのくらい簡単かというと次のコードで実装できるくらいです。

import slackweb


slack = slackweb.Slack(url="Incoming Webhookで発行したURL")
slack.notify(text="python to slack")

 Slackに登録し、チャンネルを扱える状態で Incoming Webhook | Slack App ディレクトリへ移動。設定を追加で移動した画面からインテグレーションの追加を押すと、URLが発行されます。


 後は先のコードの様に通知を送るだけです。

著者:杉浦

【Vue.js】Vue.jsに関する色々が置かれているリポジトリawesome-vue

vuejs/awesome-vue: 🎉 A curated list of awesome things related to Vue.js
 awesome-vueはVue.jsに関する様々なモノのリストが置かれたリポジトリです。ライブラリはもちろん、教材Vue.jsが使用されたプロジェクトもあります。
 awesome-vueのオーナーは開発の元締めであるvuejsそのものです。そのためか現在もとても活発にリストが更新されており、その多くが有用です。(草の根ライブラリやスニペットを探すと無視できないバグを持っていることがままあります)

 リンクと概要のみを大雑把な分け方で1ページにずらっと並べているの探すのは気持ち手間ですがリンク先は2000を優に超えており、とりあえず何か探したい時に見てみる場所として満足に使えます。

著者:杉浦

ライブラリ等の英語ドキュメントの和訳サイトにほんご。の紹介

| にほんご。
 コード中でライブラリを用いるために英語ドキュメントの中身を理解する必要がでてくる時があります。google翻訳は便利ですが、ソースコードや固有名詞の和訳するべきでない部分も和訳することがあります。自分自身で和訳しながら読むのは日本語を読むより時間がかかります。”にほんご。”はそういった英語ドキュメントを和訳しているサイトです。ドキュメントの内容は最新ではないですが最終更新日が2019.01.25と精力的に活動しています。
 にほんご。では多くのオプションを持ち、成熟したライブラリのドキュメントのAPIを扱っています。最近ではChart.jsのページが特に役に立ってくれました。

著者:杉浦

【Laravel】artisan view:cacheでBladeの中身を読めるようにする

 LarvelはBladeなるテンプレート言語を備えています。
Bladeテンプレート 5.7 Laravel
 便利は便利なのですが独自な上、説明もざっくばらんなので詳細に中身を把握するのは難しいです。
 artisan view:cacheはresources以下のBladeファイルのキャッシュを生成するコマンドです。このコマンドで生成されるPHPファイルはBladeをPHP言語にコンパイルした結果です。コンパイル後のファイルを読むことでBladeが何をやっているのか少しわかりやすくなります。
 例えば次のコードをコンパイルすると

@section('header')
@endsection
@yield('header')

@section('footer')
@show

@section('body')
@overwrite
@yield('body')

次の様になります。

<?php $__env->startSection('header'); ?>// @section
<?php $__env->stopSection(); ?>// @endsection
<?php echo $__env->yieldContent('header'); ?>// @yield

<?php $__env->startSection('footer'); ?>// @section
<?php echo $__env->yieldSection(); ?>// @show

<?php $__env->startSection('body'); ?>// @section
<?php $__env->stopSection(true); ?>// @overwrite
<?php echo $__env->yieldContent('body'); ?>// @yield

 こうやってPHPコードに直すとやっていることを追いかけられるようになり、中身を知ることが出来ます。
 @endsection, @yield, @show, @overwriteはそれぞれ@sectionと密に関わるディレクティブです。
Bladeテンプレート 5.7 Laravel#レイアウト定義
 @endsectionはsectionで定義を始めたコードの終点を示し、@yieldはsectionの出力を示し、@showはsectionで定義を始めたコードの終点であり即時展開を示し、overwriteは同名sectionの上書きを行うsection定義の終点を示します。これだけだとどういうこととなりますがコードを読むと少しわかります。@sectionの中身であるstartSectionの内容が次です。

    /**
     * Start injecting content into a section.
     *
     * @param  string  $section
     * @param  string|null  $content
     * @return void
     */
    public function startSection($section, $content = null)
    {
        if ($content === null) {
            if (ob_start()) {// ここが大事
                $this->sectionStack[] = $section;
            }
        } else {
            $this->extendSection($section, $content instanceof View ? $content : e($content));
        }
    }

 obstart()でこれ以降の出力(ブラウザに映る予定のHTMLコードなど)をバッファにため込みます。これは改めて命令されるまで出力されません。ついで@endsection,@overwriteの中身であるstopSection()が次です。

    /**
     * Stop injecting content into a section.
     *
     * @param  bool  $overwrite
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    public function stopSection($overwrite = false)
    {
        if (empty($this->sectionStack)) {
            throw new InvalidArgumentException('Cannot end a section without first starting one.');
        }

        $last = array_pop($this->sectionStack);

        if ($overwrite) {
            $this->sections[$last] = ob_get_clean();
        } else {
            $this->extendSection($last, ob_get_clean());
        }

        return $last;
    }

 ob_get_clean()でバッファの内容を取得し、出力バッファを削除します。@overwriteはこの$overwrite=trueでstopSectionを呼び出しています。これにより問答無用でプロパティsectionsの末尾にバッファを書き込んでいます。@overwriteに引数がないのは必ず末尾を参照していたせいです。
 $overwrite=falseの時はextendSection()へ回って、もし以前に定義していればそちらを呼び出すようにしています。
 @showの中身であるyieldSection()、@yieldの中身であるyieldContent()が次です。

    /**
     * Stop injecting content into a section and return its contents.
     *
     * @return string
     */
    public function yieldSection()
    {
        if (empty($this->sectionStack)) {
            return '';
        }

        return $this->yieldContent($this->stopSection());
    }
    
    /**
     * Get the string contents of a section.
     *
     * @param  string  $section
     * @param  string  $default
     * @return string
     */
    public function yieldContent($section, $default = '')
    {
        $sectionContent = $default instanceof View ? $default : e($default);

        if (isset($this->sections[$section])) {
            $sectionContent = $this->sections[$section];
        }

        $sectionContent = str_replace('@@parent', '--parent--holder--', $sectionContent);

        return str_replace(
            '--parent--holder--', '@parent', str_replace(static::parentPlaceholder($section), '', $sectionContent)
        );
    }

 yieldSectionは短縮形の様なものです。stopSection、yieldContentの順に呼び出し、最後に宣言したsectionを閉じ、出力しています。
 yieldContentはプロパティ$this->sectionsの中から目当てのsectionを選び、整形して返しているのみです。
 @endsection, @yield, @show, @overwriteはBlade内部では糖衣構文として役割に応じた大きく違う名前を与えられていましたが、実際にはちょっとした違う引数で呼び出される同じ関数らであることがわかります。

著者:杉浦

【JavaScript】フレームワーク環境付きオンラインエディタCodeSandbox

CodeSandbox: Online Code Editor Tailored for Web Application Development
 CodeSandboxはJsFiddleなどと同じJavaScript用のオンラインエディタです。CodeSandboxの特徴は次の画像の様にフレームワークを使うことを前提とした部分です。

 上画像にある様にJavaScriptのフレームワークを広くカバーしており、サーバサイド用のフィドルにもなれます。

 自分はよくVue.jsの単一ファイルコンポーネントを使うのでJSFiddleでVue.jsを操るのは少々苦手でしたが、CodeSandboxならば簡単です。一度に複数のファイルを扱えるため、単一ファイルコンポーネントを複数集めて一つのページを作る手法がそのまま使えます。

 青色のShareボタンからコードを見せるためのリンクとHTMLコードを発行することもできます。