A Day In The Boy's Life -31ページ目

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

エンジニアが色んな人と仕事をしていく中で、エンジニア泣かせのことを言われたり、「わかってくれよ」と思う場面に出くわすことはよくあります。

こういうのはその立場ごとに考え方が違うので、当然エンジニアと仕事をしていたらこういうところにイラっとくると思うことはあるんでしょうけど、ここではエンジニア視点で非エンジニアと仕事をするときにこういうところに気を回して欲しいと思うことをまとめてみたいと思います。



技術的なことを任せっきりにしない


技術的なプロとしてエンジニアを見てもらうことは結構ですが、その全てを押し付けられるとモチベーションが下がることはあったりします。

技術的な詳細は、非エンジニアの人にとっては未知なところもあるんでしょうけど、あまりにそれを任せっきりにされ「あとはいいようにしといて。細かいところは空気読んで作って」という雰囲気が出ていたりして、エスパーでもない我々にとっては頭が痛くなることばかりです。


例えば、テストであっても単体・結合テストレベルであればエンジニアしか対応できませんが、運用・受け入れテストにもろくに参加せず、リリース直前または直後にシステムを見て意見を言われたりすると「その機会はあったんだからもっと先に言ってよ」ということになるでしょう。

そういったフィードバックというのはおおかた当初に決めた仕様とは異なる意見であったりして、そもそもが仕様と違うこと言ってるじゃねぇかという気分にもなりますし、プロジェクト参加者の権威によって一気に覆ったりする(現場でまとめた仕様をPJリーダーが覆し、PJリーダーが言ったことを部長が覆し、それを役員が覆すとか)から収拾がつかなくなるケースもあったりします。


また、開発というのは結構孤独であったり重圧を受けることもあります。

上流工程が終わればあとは開発を待つだけとエンジニアだけが取り残されたりしますし(そしてお上は終わった気分になってたり)、納期のプレッシャーだけでなく技術的に実装可能かという課題にも苛まれます。

よくありがちなのが、「このサイトと同じような機能を作って」というもので、よくあるものだったらいいのですが、技術的にどう実装しているのかよくわからない場合であったり、何を使えば作れるのかがわかってもそのライブラリやツールを使った経験が無いことから納期内にうまくそれを取り込めるかやってみないとわからないということも多々あります。


このサイトと同じという「このサイト」がGoogleだったりして、これを作れるのはGoogleのエンジニアの能力や規模感や資金があるからできるわけであって、うちのこの小さなチーム(または個人)でそれをやるのにどんだけの工数かかると思ってるんだと、そのサラッと言われたことにイラっときたりするわけです。



プロジェクトの運営までを押し付けない


いざ開発が始まっても、それにどれだけの工数がかかり何時ぐらいに何ができるかというスケジュール(そして、それが可能かどうか)はエンジニア側の言い分によってある程度決まってきたりします。

ただ、そのスケジュールというのはあくまで仕様どおりの構築をするための線表であって、こちらとしては作る側で手一杯の状態であるのに、プロジェクト運営の課題やリリース前後の運用にまで仕事を回されることがあったりします。


例えば、構築するシステムに対する運用の組み立てができてなかったり(それによって仕様の変更に影響する恐れがあったり)、マイルストンがブレブレであったり(フェーズをわける考えが無くて全てをリリース時に盛り込もうとしたり)、現場への説得や説明にまわされたり(それによってタスクには含まれない成果物を求められたり)。


確かに技術的な要素については不明点も多いのかもしれませんが、そもそも作りたいと提案してきた側が明らかにそれをコントロールするイメージをもてておらず、想定のシステムができたら全てがうまく回ると思っていたりして、こちらとしてはそれをどう使っていくのかというその後が全く想定できていない現状を垣間見ると不安と心配が錯綜し、余計なところまで手を出さざるを得ない状況になったりします。

まぁ、こういうことはプロマネであったりプロジェクト運用担当者の能力に依存する部分なんでしょうけど、明らかにエンジニアとしての仕事ではない部分まで対応する羽目になったりするとイラっときたりするわけです。



エンジニアの美学を尊重する


こういうのは個人差があったりそもそもそういったことにこだわらない人もいるのですが、エンジニア独自の美学というのを持っている人もいたりして、それを否定することを嫌う人も多かったりします。


例えば、保守性を高めようとコードを綺麗に書いたりリファクタリングすることを理解できずに時間の無駄と捉えられたり、十分なテストをクリアしていないものをリリースさせたくないと思っているエンジニアもいる中で納期優先で「とりあえずリリースして」といってくる人もいたりします。

やはり目に見える画面だけでなくその裏のロジックもきっちりしたいと思っているエンジニアもいますし、取りあえずで作ったものが本格運用され規模が大きくなるにつれて手を加えることが難しくなり果てには自分たちを苦しめる結果になることをよく知っている人もいますし、品質が保証されてないものをリリースしバグが出ることで自分たちの能力を計られたりその改修で多くの工数を保守で費やす羽目になることを経験している人たちもいます。


自分はB型の割りに結構まめな方かなと思ったりしているんですが、結構システムの構成要素の作る順番とか決めているのを崩されたりするのが嫌だったり(もちろんそのマイルストンは事前に承諾を貰っているのに度外視されたり飛込みだったりして)、先に書いた取りあえずリリースしてと言われるのが嫌だったり(スモールスタートという意味ではなく取りあえず使いたいという意見だったり)、そもそも自分たちで物づくりすることに否定的だったりする(外注頼り)意見を言われるのが嫌だったりします。

まぁ、何れにせよエンジニア視点でよいものにしたいという考えがあるわけで、それを否定されることがあったら身構えちゃうわけです。


そういったものへの防衛本能や、コードそのものをある種芸術作品のように捉えているエンジニアもいて、自分が納得するまで妥協したくないと思っている人もいる中でそれを否定されるとイラっときたりするわけです。



まとめ


最初に書いたように逆の立場からエンジニアに言いたいことも多々あるでしょうから、双方で考え方の歩みよりは必要なのでしょう。

ただ、エンジニア独自の考えに配慮することでプロジェクト運営もスムーズに運べるようにはなるのではないかと思ったりするわけです。


エンジニアへのプレッシャーや役割への理解やそもそも言い分として矛盾している点がないかとか、よく考えるとエンジニア云々の話でもない点も多々あるわけで、やっぱり参加する人をきちんと見てその人に応じた対処や配慮をすることは大事なんじゃないかなと思うわけです。






Apache Solrのschema.xmlを読み解く 」にて少し書いたことですが、Apache Solrの設定ファイル(schema.xml)には様々なフィルタが設定可能で、それによって検索結果が大きく異なってきます。

フィルタの数は多数あって、利用用途やトークナイザーによって変わってきますのでここで書いているのは一部なんですが、こんな検索にヒットさせたいといった場合にフィルタの使い方次第で検索精度を高められたりするので、どういったフィルタがあるのか知っておけば用途に応じて使い分けられて便利です。


ここではApache Solrはバージョン4.6.1を使ってフィルタの検証をしています。



filterとcharFilter要素の違い


フィルタ(filter)要素はschema.xmlファイルのfieldType要素内に定義していきますが、charFilterという要素もあったりして少し混同したりします。

charFilterは、文章を解析する前処理としてデータの加工をするためのフィルタを指定する要素で、文字を置換するためのルールを定義しそれに従って変換するsolr.MappingCharFilterFactory、正規表現でパターンを指定しそれに従って変換するsolr.PatternReplaceCharFilterFactory、HTMLタグを取り除き中の文章だけを抜き取るsolr.HTMLStripCharFilterFactoryなどが標準であります。


HTMLStripCharFilterFactoryはSolrへのデータ投入前にデータ加工しておけばよいのではと思ったり、PatternReplaceCharFilterFactoryは正規表現を使うということでパフォーマンスに影響が出るって記事も見たりするので実質手軽に使えそうなのがMappingCharFilterFactoryぐらいなのかなと思ったりしますが。

MappingCharFilterFactoryの使い方は、


<charFilter class="solr.MappingCharFilterFactory" mapping="mapping.txt"/>

というようにマッピング用の定義ファイル(mapping.txt)を指定します。

mapping.txtは、下記のように変換ルールを定義します。


"0" => "0"
"1" => "1"
"2" => "2"
"3" => "3"
"4" => "4"
"5" => "5"
"6" => "6"
"7" => "7"
"8" => "8"
"9" => "9"

上記の例は、全角の数字を半角に変換するルールを書いています。

これにより、全角数字でも半角数字でも同じ検索結果を返せるようになったりします。

もう1つの効用として、トークナイザに形態素解析(solr.JapaneseTokenizerFactory)を使っている場合、下図のように全角数字はトークンを1つ1つに分解してしまうようで、事前に半角数字に変換することでトークナイザーに対して正しいトークンとして認識させることができたりします。


SolrAnylyze-1


charFilterはこのように文章をトークンに分割する際のルールを定義するもののため、tokenizer要素より前に指定する必要があります。
fiter要素とあわせるとその指定できる順序は、charFilter → tokenizer → filterという順番になります。


<fieldType name="text_ja" class="solr.TextField">
<analyzer type="index">
<charFilter class="solr.MappingCharFilterFactory" mapping="mapping.txt"/>
<tokenizer class="solr.JapaneseTokenizerFactory" mode="search" userDictionary="lang/userdict_ja.txt"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms_index.txt" ignoreCase="true" expand="true"/>
</analyzer>
</fieldType>


フィルタの種類とその用途


本題のSolrのフィルタの種類ですが、下記のようなものがあります(一部です)。

先に書いたように、フィルタはトークナイザの種類によって使えるものと使えないものがあったりして、ここで列挙しているのは形態素解析(solr.JapaneseTokenizerFactory)を使っている場合に使えるフィルタですので注意してください。

あまり情報が無かったりして検証結果で書いている部分もあるので適当なところがあるかもしれません。


・ solr.SynonymFilterFactory


類語を登録できるフィルタです。


<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>

元々サンプルファイルがあるのでその中を見ればわかりやすいのですが、


aaafoo => aaabar
GB,gib,gigabyte,gigabytes

というように、「aaafoo」という単語を「aaabar」に置換したり、「GB」と「gigabyte」を同じ意味になるように定義しています。


SolrAnalyze-2


上記のようにSF(SynonymFilter)でaaafooがaaabarに置き換えられていることがわかります。

同義語や略語を登録したいという場合に便利だったりします。

ただ、英語の場合は一綴りで書いた単語はトークンが1つと認識してくれるのでよいですが、日本語を扱う場合は注意が必要でトークナイザがトークンに分割する際に同義語に定義した単語がそもそもきちんと認識してくれないケースがあったりします。


情報システム部,情シス

というような定義をしていてもトークナイザが下記のようなトークンに分割してしまい、「情報システム部」の同義語を登録していても正しく認識されません(「情シス」という単語も同様)。


SolrAnalyze-3


こういった場合は、事前に辞書データ(lang/userdict_ja.txt)にこれらの単語を登録してSolrに認知させる必要があります。


・ solr.JapaneseBaseFormFilterFactory


動詞とかを基本形に直してくれるフィルタです。


<filter class="solr.JapaneseBaseFormFilterFactory"/>

例えば「動いた」を解析してみると


SolrAnalyze-4


のような結果が返ってきます。

まぁ、取りあえず日本語使う場合は使っておけって言うフィルタですかね。


・ solr.JapanesePartOfSpeechStopFilterFactory


余計な品詞を検索対象にしないためのフィルタです。


<filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" enablePositionIncrements="true"/>

設定ファイルを見てみると助詞や記号などを除外するようにしているみたいです。


・ solr.CJKWidthFilterFactory


全角の英字を半角にしたり、半角カタカナを全角にするフィルタです。

全角の数字は対象になっていないようで、先に書いたMappingCharFilterFactoryを事前に通すといった処理が必要です。


<filter class="solr.CJKWidthFilterFactory"/>

Solrの管理機能で解析してみると半角カナの場合は確かに全角に変換されてヒットしている表示になるのですが、全角大文字の英字になると半角に変換されているようですがヒットしているようには表示されません(全角でも小文字の英字だとヒットしているように表示される)。

ただ、その他のフィルタの文字を見るときちんと半角に変換された英字が対象になっているので、機能としてはきちんと働いており単に表示の不備なのかもしれません。


・ solr.StopFilterFactory


名前の通り、インデックスさせたくない単語を登録できるフィルタです。

「て」「に」「を」「は」といった助詞や、「によって」「において」「に関して」など要は検索する上では邪魔になる単語を排除してくれます。


<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" />


オプションのignoreCaseは英字の大文字小文字を区別するかどうかというものになります。

このフィルタは、もちろん検索されたくない、インデックスさせたくないといったNGワードを登録することもできます。

例えば、「itboy」をNGワードとして登録しておき、「itboyさんは素敵です」の検索結果は下記のようになります。


SolrAnalyze-5


見事に消されているのがわかります。


・ solr.JapaneseKatakanaStemFilterFactory


カタカナの長音(ー)を消してくれるフィルタです。

「コンピューター」と「コンピュータ」など人によって使い方が違ってくる言葉を統一してくれます。


<filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>

オプションのminimumLengthは対象とする最大文字数を指定するもので、上記の場合4文字以上が対象となるため、「ソニー」が「ソニ」になることはありません。


SolrAnalyze-6


・ solr.JapaneseReadingFormFilterFactory


日本語の読みを検索対象にするためのフィルタです。

例えば、「日本」に対して「にっぽん」でも「ニッポン」でも読み方に対してヒットさせることができたりします。

(「日本」は「ニッポン」で登録されているため、キーワードが「ニホン」ではヒットしません。これをヒットさせたい場合、SynonymFilterFactoryにて類語登録をしておけばヒットさせることができます)


<filter class="solr.JapaneseReadingFormFilterFactory" useRomaji="false"/>

オプションのuseRomajiはローマ字も読みの対象にするか指定するもので先の例で言えば、「nippon」でもヒットさせることができるようになります。


・ solr.ICUTransformFilterFactory


カタカナを平仮名に変換できたりするフィルタですが、先ほどのJapaneseReadingFormFilterFactoryでも同じようなことができるのであまり詳しく調べてません。


<filter class="solr.ICUTransformFilterFactory" id="Katakana-Hiragana"/>

ちなみにこのフィルタはそのままだと使えないので、下記のようにライブラリをSolrへ事前にロードさせる必要があります。


まず、使いたいSolrコアのディレクトリ以下にlibディレクトリを作っておきます。

次に、そのlibディレクトリに下記のjarファイルをコピーします。


$ cp /path/to/solr/contrib/analysis-extras/lib/icu4j-49.1.jar /path/to/solr/example/solr/coreName/lib/
$ cp /path/to/solr/contrib/analysis-extras/lucene-libs/lucene-analyzers-icu-4.6.1.jar /path/to/solr/example/solr/coreName/lib/

最後にSolrを再起動します。


・ solr.LowerCaseFilterFactory


こちらは、英字を小文字にすることで大文字・小文字を使い分けた表記ゆれを統一することができるフィルタです。


<filter class="solr.LowerCaseFilterFactory"/>

「solr」でも「SOLR」でも「Solr」でもヒットさせることができるようになります。



フィルタは結構数が多いですし、Javaが書けたら自作もできるようですので色んな有識者が作成したものが公開されていたりします。

検索の用途や精度に応じて色々試してみたら実現したい検索システムを手早く作れるかもしれません。





PHPというよりはSolrネタではあるのですが、Apache SolrにはApache Tikaというドキュメントのメタデータを抽出するソフトウェアが同梱されています。

Apache Tikaを使えば、WordやPowerPoint、PDFなど様々なドキュメントの本文を取り出し、それをApache Solrへ取り込んで検索対象にするといったことが可能になります。

利用イメージは下記のような感じで、今回のプログラムは赤枠の部分をするためのものです。


Apache Tikaを使ってSolrへインポート


今回利用しているApache Solrのバージョンは4.6.1、PHPは5.3系を使っています。



Apache Tikaからドキュメントのメタデータを抽出するPHPプログラム


Apache Solrの中にあるApache TikaへのアクセスはRESTなAPIが提供されているため、ここに示すPHPでなくても簡単なプログラムで利用可能です。

CURL関数を使って書いていますので、それこそ同様の処理をシェルスクリプトからとかでも作れたりします。


<?php

// Apache Solrが使うポート番号
$solrPort = "8983";

// Solrのコア名
$solrCore = "collection1";

// 実行ホストのドメイン
$domain = "localhost";

// Apache Solr(Tika)のURL
$solrUrl = "http://" . $domain . ":" . $solrPort . "/solr/" . $solrCore . "/update/extract?extractOnly=true";

if (empty($argv[1]) || !file_exists($argv[1])) {
    echo "ディレクトリが存在しません" . PHP_EOL;
    exit;
} else {
    $dir = $argv[1];
}

// cURL初期化、ヘッダ設定
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $solrUrl);
curl_setopt($curl, CURLOPT_BINARYTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);

if (is_dir($dir) && $dh = opendir($dir)) {
    while (($file = readdir($dh)) !== FALSE) {
        if ($file === "." || $file === "..") {
            continue;
        }
        // 読み込み対象ファイル
        $targetFile = $dir . $file;

        $data = array('name' => $targetFile, 'file' => '@' . $targetFile);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);

        $result = curl_exec($curl);
        if (curl_errno($curl)) {
            $info = curl_getinfo($curl);
            if (empty($info['code'])) {
                $code = "ホストに通信できません";
            } else {
                $code = "HTTP Code:" . $info['code'];
            }
            $msg = "通信エラー: " . $code . " url:" . $info['url'];
            echo $msg . PHP_EOL;
            exit;
        }

        // 解析結果のXMLをパース
        $xml = simplexml_load_string($result);

        $body = strip_tags($xml->str);
        echo $targetFile . PHP_EOL;
        var_dump($body);
    }
    closedir($dh);
}


コマンドラインで動かす想定にしているため、第一引数にドキュメントが格納されているディレクトリを指定すればその中にあるファイルを拾ってApache Tikaへ投げ、解析結果を受け取るというような流れで動きます。

Solrのポート番号やコア名などは必要に応じて変更してください。


$ php import.php /path/to/docdata/

結果はXML形式で受け取っていますが(パラメータ無しの場合のデフォルト)Apache Solrの検索と同様にパラメータをつける事でJSON形式やCSVなどで受け取ることができたりします。


// Apache Solr(Tika)のURL
$solrUrl = "http://" . $domain . ":" . $solrPort . "/solr/" . $solrCore . "/update/extract?extractOnly=true&wt=json";

上記はJSON形式で結果を受け取りたい場合で、wtパラメータに所定の形式をセットしてAPIに問合せます。

受け取ったメタデータをその後、どう加工したいかで必要に応じて変えてみるのがよいのではないでしょうか。


ということで、Apache TikaへのAPIに対して、送信に必要な設定の上で抽出したいドキュメントデータをPOSTしてあげるだけで結果が受け取れます。

ドキュメントデータにパスワードがかけられているものもあったりしますので、そういう場合はHTTPステータスコードが500で返ってきたりしますので必要に応じてエラーハンドリングしておくのがよいでしょう。


PDFやWordなどオフィス系のファイルの中身を取り出してDBに格納したいとか、表示したいという場合に簡単に抽出ができるのでApache Tikaの利用を検討してみてもよいかもしれません。