読者です 読者をやめる 読者になる 読者になる

09

Java EE でPlugin的な拡張ってどんな扱いなの

あるウェブアプリケーションを提供する場合にプラグイン的な機構を設けて、利用するユーザごとに機能拡張であるとかカスタマイズさせたい場合、なんていうユースケースは昨今いろいろありそうではあるけど、Java EE的にそういった機構を実現するための仕様ってあるんだろうか。 基本的にウェブアプリケーションはWarという形でまとめられてしまうからそこ以外からの干渉はできない気がする(むしろ独立した環境がパッケージングされているということに意味がある感じがする) 例えば単純にあるインタフェースを持ったクラスを運用中に追加して、アプリケーションの動作を変更するなり拡張するなりができる、とした場合、追加のClasspathというか追加のクラスローダが必要になる気がするのだけどそれはアプリケーションが独自にやればいいという話でJavaEEでは仕様化されていないのかな。

ただ全体としてはJavaのコードだけではダメでクライアントサイドスクリプトとかイメージのようなスタティックファイルなんかも動的に追加したいとなった場合、どうなんだろう。それ専用のコンテンツローダを作るのだろうか。

アプリケーションサーバによってはサーバ側の設定でやれそうではあるけど。

com.caucho.java.CompileClassNotFound: illegal utf8 encoding at (140)

e-Builder 7.2.x でデバッグサーバを起動しようとして以下のような例外で止まる。 昨日までは動いてたのにな?

警告: com.caucho.java.CompileClassNotFound: illegal utf8 encoding at (140)
com.caucho.java.CompileClassNotFound: illegal utf8 encoding at (140)
    at com.caucho.loader.CompilingLoader.compileBatch(CompilingLoader.java:749)
    at com.caucho.loader.CompilingLoader.make(CompilingLoader.java:353)
    at com.caucho.make.MakeContainer.make(MakeContainer.java:70)
    at com.caucho.loader.DynamicClassLoader.make(DynamicClassLoader.java:1134)
    at com.caucho.loader.EnvironmentClassLoader.scan(EnvironmentClassLoader.java:526)
    at com.caucho.loader.DynamicClassLoader.sendAddLoaderEvent(DynamicClassLoader.java:746)
    at com.caucho.loader.DynamicClassLoader.init(DynamicClassLoader.java:1163)
    at com.caucho.loader.EnvironmentClassLoader.init(EnvironmentClassLoader.java:186)
    at com.caucho.loader.ClassLoaderConfig.init(ClassLoaderConfig.java:156)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.caucho.config.j2ee.PostConstructProgram.inject(PostConstructProgram.java:60)
    at com.caucho.config.type.BeanType.init(BeanType.java:226)
    at com.caucho.config.ConfigContext.configureChildNode(ConfigContext.java:522)
    at com.caucho.config.ConfigContext.configureAttribute(ConfigContext.java:327)
    at com.caucho.config.program.NodeBuilderChildProgram.inject(NodeBuilderChildProgram.java:56)
    at com.caucho.config.program.ContainerProgram.inject(ContainerProgram.java:80)
    at com.caucho.config.program.ConfigProgram.configure(ConfigProgram.java:61)
    at com.caucho.server.deploy.EnvironmentDeployController.configureInstance(EnvironmentDeployController.java:364)
    at com.caucho.server.deploy.EnvironmentDeployController.configureInstance(EnvironmentDeployController.java:55)
    at com.caucho.server.deploy.DeployController.startImpl(DeployController.java:665)
    at com.caucho.server.deploy.StartAutoRedeployManualStrategy.startOnInit(StartAutoRedeployManualStrategy.java:74)
    at com.caucho.server.deploy.DeployController.startOnInit(DeployController.java:549)
    at com.caucho.server.deploy.DeployContainer.start(DeployContainer.java:160)
    at com.caucho.server.webapp.WebAppContainer.start(WebAppContainer.java:659)
    at com.caucho.server.host.Host.start(Host.java:450)
    at com.caucho.server.deploy.DeployController.startImpl(DeployController.java:667)
    at com.caucho.server.deploy.StartAutoRedeployAutoStrategy.startOnInit(StartAutoRedeployAutoStrategy.java:72)
    at com.caucho.server.deploy.DeployController.startOnInit(DeployController.java:549)
    at com.caucho.server.deploy.DeployContainer.start(DeployContainer.java:160)
    at com.caucho.server.host.HostContainer.start(HostContainer.java:484)
    at com.caucho.server.cluster.Server.start(Server.java:1319)
    at com.caucho.server.cluster.Cluster.startServer(Cluster.java:710)
    at com.caucho.server.cluster.ClusterServer.startServer(ClusterServer.java:542)
    at com.caucho.server.resin.Resin.start(Resin.java:703)
    at com.caucho.server.resin.Resin.initMain(Resin.java:1162)
    at com.caucho.server.resin.Resin.main(Resin.java:1365)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at jp.co.intra_mart.bin.server.core.WebApplicationServer.run(WebApplicationServer.java:259)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.CharConversionException: illegal utf8 encoding at (140)
    at com.caucho.vfs.i18n.UTF8Reader.read(UTF8Reader.java:158)
    at com.caucho.vfs.i18n.UTF8Reader.read(UTF8Reader.java:184)
    at com.caucho.vfs.AbstractByteToChar.readChar(AbstractByteToChar.java:153)
    at com.caucho.vfs.AbstractByteToChar.addChar(AbstractByteToChar.java:125)
    at com.caucho.java.JavacErrorParser.parseErrors(JavacErrorParser.java:74)
    at com.caucho.java.ExternalCompiler.compileInt(ExternalCompiler.java:218)
    at com.caucho.java.AbstractJavaCompiler.run(AbstractJavaCompiler.java:102)
    ... 1 more 

デバッグサーバを再構築したりワークスペースを切り替えたりしたけどダメで結局e Builder再インストールしたら直った。

clojure 書いてみた日記

intra-mart Web Platform 7.2.x では以下のようなプレゼンテーションページを書いた場合

<html>
  <body>
     <!-- <imart type="string" value="hoge" ></imart>--> 
  </body>
</html>

コメントの中のimartタグは評価されず、そのまま出力される。 つまり、クライアント側に <imart type="string" value="hoge" ></imart> が(ソース表示すると)そのまま見える。 これが intra-mart Accel Platform になると挙動が変わっており、コメントの中のものも評価されるようになっている。 つまり上記を intra-mart Accel Platform で実行した場合クライアントに対して出力されるのは

<html>
  <body>
     <!-- hoge -->
  </body>
</html>

というHTMLになる。この例だと多分最終的な見た目は変わらないからいいんだけど、条件分岐を評価させないようにというつもりでコメントアウトしていると厄介なことになる。

<html>
  <body>
     <!-- <imart type="condition" validity=oValue >--> 
     必ず出力したい文字
     <!-- </imart> -->
  </body>
</html>

intra-mart Web Platform 7.2.x では必ず出力したい文字は出力されるが、 intra-mart Accel Platform ではoValueの値によっては出力されなくなる。

これがintra-mart Web Platform 7.2.x から Accel Platform へ移行する際に困った問題の一つ。

一応正規表現を使って一括である程度対応できるものの、完全にチェックするのはなかなか難しい。 正規表現仙人であれば可能なのかもしれないが、個人的にはその域に達していないので、 どうにもこれが残ってしまっていないか不安だった。

一応実案件ではテストもするので、必要な対応はできていたと思っているけど、まあ簡単にスクリプトでも書けば 精度も効率も上がるだろうなと思っていて、一つスクリプトを書いてみることにした。 言語は長いことかけてのんびり勉強している Clojure。

要件

やりたいことを予め上げておくと

  • HTMLコメントに隠された imart タグを探す。
  • 多分問題になりうる文字列は<imartとか</imart>とか。
  • 特定のフォルダ配下を一括してチェックしてファイルパスと発見した行数と内容を出力したい。
    • 勝手に問題が解消するよう編集してくれてもいいけど、怖いのでそこまではしないでおこう

成果物

まあどの程度正確にやれてるかあやしいんだけど一応以下に上げてみた https://github.com/carrotsword/commented-imart-detection

ご査収ください。

タグの文字列をある程度固定で探しているんだけど、空白なんかを正規表現で あいまいにマッチさせた方がいいのかもしれない。

Clojureについて

Clojure の勉強用に書いてみたものなので、至らない点は多々(テストは書いているけどまだ実戦に投入はしていないので)あると思うけど Clojureを書きながら普段Java/Javascriptプログラマである自分がどんなことを考えたかということを残しておきたい。

不変性(immutability) と 関数指向

基本的にはデータをどういう集合としてとらえて、ある集合から別な集合への変換をどう考えるか、 ということを考えるのに終始していた気がする。

普通にJavaでプログラミングする場合には状態の遷移はなるべく状態のライフサイクルと マッチするようなオブジェクトを(クラスとして)設計して、状態の管理はそのオブジェクトの 中に閉じ込めて考える、という感じに設計していくだろう。

(多分関数型全般の)特性として、基本的にそういう考え方ができない(あるいは推奨されない)。 データ=状態で、状態を直接変更することは許されない(わけじゃないんだけど)から、 状態を変更するという操作はある状態Aをもとに別の状態Bを生成する形になるんだろう。

その状態遷移を記述するのが関数ということになる。

なので、基本的に状態遷移の時系列がコード上は関数の羅列という形で記述される。

オブジェクト指向の場合は状態の遷移に関する操作が一か所に集まるような 設計になる(もちろんならない人もいるが)のが普通だろうが、これは必ずしも 状態の遷移を明確に記述することにはならない。 (状態の遷移の時系列は一般にオブジェクトの外側からどう呼ばれるか次第なので、 そのオブジェクトの中にはない場合が多い)

状態Aから状態Bに遷移させる関数の適用を A => B と表現するならば、コード上の記述は

A => B
B => C
C => D

といった記述になるだろう。Clojureは動的型付け言語だけど、静的型付な環境であれば、 上記のA~Dはそれぞれ異なる型となるのだろうか。 そうであれば、型さえわかればできることは自明になるのかもしれない。

ただまあ、とにかくすべてを上記のように割り切って考えられるというわけでもない。

標準ライブラリ

とにかく googlability が低いという印象。 Clojureのコードを書いているときはMacならDash、 Windowsならブラウザで公式サイトを開きっぱなしにして調べつつコードを書いていた。

まあ、悪くないんだけど、今まさに学習中なのに、自分が知らない概念にたどり着くのが難しい感じがする。 Javaだったらこう書くんだけど、をGoogle先生に聞いて、結局自分の中のJavaコードをClojureにマッピングしているだけなのではという感じがあるのだけど、たまに聞いたこともないClojure用語的なものが出てきて、適当に検索してみてもなんのことか全くわからないということがある。

自分の知らない概念がまだまだあるんだろうなあ、という漠然としたイメージだけはある。まあドキュメント上からちゃんと読めということなのかもしれない。

S式

あるデータhogeに対してAしてBしてCしたいという場合にはまあこうなる

(C (B (A hoge)))

スレッドマクロというのがあって、こう書ける

( ->> hoge
  (A)
  (B)
  (C))

分かりやすい! が、それでいいんだっけ?という気持ちになる。 結局S式わかりにくい。

REPLとかコーディングのワークフローとか

REPLで小さな関数をどんどん試しながらボトムアップにコードを書いていくのがいいらしいのだけど、その辺の感覚がまだわからない。 頭の中のコードをとりあえずガーッと書き出してしまう感じ。なので実際動かしてみるといろいろ思っていたのと違う結果になってしんどい。

デバッグの方法もまだあまりしっくりきていない。以下の記事は大いに参考にさせてもらったのだけどEmacs使いでもないのでな・・・。

ayato.hateblo.jp

遅延シーケンス

頭ではわかっていても、実際経験してみないとわからないというか、気を付けられないものだなというのを改めて知った感じ。

Java VM の読み込み中にWindowsのエラー2が発生しました

今更ではあるけど、最近のPCの環境にintra-mart e-Builder 7.2.x をインストールしようとすると、タイトルのエラーが表示されてインストールできない。 どうもこれと類似の問題のようだ

www-01.ibm.com

インストーラがInstallAnywareを使って作られていて、それがもうだいぶ古いのか、最新のJava8の入っている環境ではうまく動かないようだ。

LAX_VM オプションで使用するVMを指定することができる。

C:\> eBuilder726.exe LAX_VM "C:\Program Files (x86)\Java\jdk1.7.0_55\bin\java.exe"

ちなみにここで必要だったか正確に覚えていないが、どこかの時点で32bitVMがないとやはり途中で失敗したはず。

Windows 10だとさらにオプションが必要だった

C:\> eBuilder726.exe LAX_VM "C:\Program Files (x86)\Java\jdk1.7.0_55\bin\java.exe" -i GUI

なんでだかよくわからないけど。

そなえよう

ロジクール G810 RGB Romer-G メカニカル ゲーミング キーボード

アフィリエイト張ってみたかったのでやってみるコーナーです。

プログラマなんていう職業をやっていると気分転換にキーボード変えたりマウス変えたりするわけですが、少し前に職場用にこれを買った。

Logicool ロジクール G810 RGB Romer-G メカニカル ゲーミング キーボード

Logicool ロジクール G810 RGB Romer-G メカニカル ゲーミング キーボード

なかなかお気に入り。以下具体的に気に入っている点を挙げると:

  • メカニカルという割に静か
    • 押した感じ「カチャカチャ」というよりは「さく、さく」という感じ
    • 今までCORSAIRの赤軸(後述)を使ってたんだけど、結構キーの音が大きいというか、多分底突きの音が本体に反響していて、これが結構気になる。
    • 聞き比べてもはっきりと静かになった。
  • キーが軽い
    • 多分赤軸も大分軽いのだと思いますが、それと比べても若干軽い。
    • ただRomer-Gはなんか押し下げ感にざらつきがある気がする。これはもしかするといやに感じる人がいるかもしれない。
  • 重い
    • キーボード重量がこのサイズにしてはだいぶずっしり来る感じ。まあこの価格帯のキーボードならびっくりするほどではないけど。
  • キートップがレインボーカラーに光る
    • Romer-Gの売りの一つ
    • まあ別に・・・

前まで使ってたキーボードというのはこれなんですが

CORSAIR ゲーミング キーボード K65 Compact JP

CORSAIR ゲーミング キーボード K65 Compact JP

散らかし放題なデスクトップで戦っていく必要があり、コンパクトでよかったのですが、打鍵音がカーンと響く感じで耳に残る。

ちなみにRomer-GでいうとG910というキーボードが去年、Romer-Gの初搭載機ということで発売になっていました。

LOGICOOL RGB メカニカル ゲーミング キーボード G910

LOGICOOL RGB メカニカル ゲーミング キーボード G910

これが発売された時からRomer-Gには興味があったものの、さすがにこのデザインでは辛いため見送っていたのですが、今年になってすっきしりしたのが出てきたので発売のニュースを見つけた瞬間に買ったという感じです。

ちなみにキーボードもマウスも有線派です。突然つながらなくなった時に原因が電池なのか、無線系統の不調なのかよくわからんという状態になったことはありませんか。電池を変えたりデバイスのペアリングを削除して再登録してみたり、デバイスドライバを更新したりしてなんとか復旧したものの結局電池ではなかったのか?みたいなイライラに耐えかねて無線は買わないことにしました。

ゲーミングキーボードとかゲーミングマウスばっかり買ってるんだけど、別にレイテンシが~とかキー同時20っこ押し!とかするわけではなく、単純に基本的な機能の信頼性が高いのではと思って買っている感じ。

なんでそんなにSQL嫌いなの

ORMはあまり使ったことないけど

私がWebプログラミングを学んだのは仕事上必要だったからなんだけど、多分2001年とか2002年くらいにWindowsのASP(いわゆるLegacy ASP。VBScriptで書くPHPみたいなやつ)とかJava Servlet+JSPとかがはじめだったと思う。Javaで言えば当時ちょうどStrutsとかの出始めくらいだったんじゃないかと思うんだけど、当時書いたアプリケーションでStrutsを採用したものがいくつかあった気がする。

で、当時からORMってのはあって、EJBの世界だとCMPとかだったんだろう(そのあたりは個人的にはまったく触ったこともないのでどんなものかすら知らない)

Servletの世界でもいくつかORMは出てきていて、Hibernateがなんとか使い物になりそうって感じの時期だったかな?個人的にはHibernateも使ったことはなくて、自分がStrutsと組み合わせて使っていたのはTorqueっていう、Strutsとは別のMVCフレームワークであるApache Turbine(当時は Apache Jakarta Turbineだったかな)のサブプロジェクトとして開発されていたORMだった。

Apache Jakarta Turbine

いきなり脱線するけど、このTurbineっていうフレームワーク、あまり採用したような話は聞いたことがないし、Java系の雑誌なんかでもあまり詳しく紹介されている記事は見なかった気がする。

結構巨大なフレームワークだったこともあってサブプロジェクトが多く、いまではJavaのビルドツールのデファクトとなっているMavenも、もともとはこの巨大なフレームワークを使って開発する際のひな形を生成したり、膨れ上がったコードや設定ファイルを自動的にまとめ上げてアプリケーションとしてビルドするために始まったのだったと記憶している。

他にも、いまだにたまに使われているということを耳にするVelocityというテンプレートエンジンももともとはここのサブプロジェクトだったはず。

Apache Jakarta Tuebine Torque

話を戻して、TorqueというORMの話。 当時自分のプロジェクトで何故Torqueを採用したのか、もはや10年以上も前の話なのでおぼろげにしか覚えていないんだけど考えてみた。

まずTorqueのユースケースというのは基本的にはテーブル構造(というか、たぶんオブジェクト構造だろう)をXMLで定義して、そこからRDBMSのテーブルやクラスを自動生成する、というものだったと思っている。ただそれだけではなくて、すでに構築済みのDBのスキーマ情報からメタデータを引っ張ってきて、このXMLの定義を起こし、そこからクラスを生成するという方法もサポートしていた。

.NetのEntity Frameworkのモデル・ファーストとかデータベース・ファーストとかいう手法とおなじ考え方だと思う(Entity Frameworkよく知らないけど)

ここで思うのはテーブル定義をコーディング前にやってしまうウォーターフォール脳にはちょうど良かったっというのが一つ、それともう一つがテーブルとクラスが一対一で、Joinして別のテーブルをくっつけたい場合でも結構わかりやすい関数を自動生成してくれていたことが大きな理由のような気がする。

当時IDEといえばとりあえずEclipseが使い物になるようになってきたみたいな時代だったんだけど、クラスやJoinするためのメソッドを先に生成しておいてもらえると、自動補完でどんどんコードが書ける。基本的にはテーブルから自動生成されたコードになるのでSQLを書き損じてシンタックスエラーになるなんてことが起こりにくくて品質上がるじゃん、という期待があった気がする。

その程度で上がる品質なんて今の時代で言えばJUnitで一通り流せばわかるじゃんという話なんだけど、まあ当時はそこまでJUnitでやる発想がなかったというかなんというか。あとやっぱりそういうのプロジェクトをブートストラップするための各種コストおよびメンテするコストになるのでね。極力コンパイラがバグを摘出できる構成を作り上げたいというのがあった。

なんでこれが重要かということを考えると、まあSQLの構築をJavaの世界にもってきてコンパイラで検出可能にするのが大きい。

SQLのシンタックスエラーって、シンタックスエラーなのにもかかわらず発生するとどこでどう間違ったのかがどうにも分かりずらい。日ごろからRDBMS使っている人ならわかるだろうけど、「誰(何)が」「何に対して」「何をしようとして」「何が」「どう」おかしいのか、というのが全く分からないため、デバッガで逐次実行するか、同じ状況を作って専用のSQLクライアントから流すとかしないと現象の詳細が確認できない。いや、シンタックスエラーに限らないか。前に書いたけどこれなんて何言ってるのかさっぱりわからない。

09.hatenadiary.jp

で、いざ問題のあるSQLを直そうという段になると、これがJavaのコード上では文字列の長ったらしい連結を繰り返して書かれていたりする。特にJavaだとヒアドキュメントリテラルがないのでひどいことになりがちだ。一度クエリとして実行して何が悪いか確認しようと思っても一度文字列連結処理を削除して普通のSQLに戻す、という作業が発生する。いまどきはprepared queryは当たり前に成っているから、そこからさらにパラメータをちまちま書き換えてやる必要もある。 そうしてできたSQLを流してみてエラーがわかればよいが、そのままでは再現しなかったりするとまた話が面倒になる。

今考えれば単にSQLのログ出力すればこんな苦労しなくてよさそうだ。まあ当時はそんなこともわからなかった。

・・・というような、この辺の苦労を少しでも軽くすために手書きのSQLは避けたい、というのが当時の心理だったろうな。自動生成によってそもそもSQLのシンタックスエラー的なところは防げるのではないかと…思っていた。

後日談になるけど、結局実装できる限界がすぐあらわになってしまって(つまり実際の処理を実装するにはそんなに使い勝手がよくなくて)性能的にも全然間に合わなかったので、だんだんプロジェクトからなくなっていって最終的にマスタメンテくらいにしか使わなかったな。

SQLといえば

しかしJDBC経由の場合のみかもしれないが、DBのエラーメッセージってなんであんなにわかりづらいのだろう。たとえば日付型のキャストしようとしてるけど型が間違ってます、みたいな場合、キャストしてる箇所が複数あったりしたときにどっちが問題か、とか、どういう値が問題だったか、ぐらい報告できないのだろうか。この問題に関してはここ10年以上進捗していない気がしているのだが。

DBのエラーメッセージがわかりやすいだけで生産性がずいぶん違うんではないかと思っているのだけどなあ。

その後はあまりコードを書かない仕事をしていた期間もあったりしてiBatisやらHibernateやらS2JDBCやらは(検証等で触ったりサイドワーク用のツールを実装してみたりとかは別として)ほぼスルーしてきていて、最近使ったのはmirage-sqlっていう、ORM(Object-Relational Mapping)というかOSM(Object-SQL Mapping)とでもいうべきライブラリ。これは一応テーブルとクラスのマッピングもやってくれるけど、どちらかというとSQLは設計要素としてちゃんとあって(つまり自動生成ではない)、SQLのI/Oをオブジェクトとマッピングするという感じ。

結局我々は思う通りのSQLを実行したい、という欲求があるので、この程度のライブラリが一番取り回しやすくてよいのではないかという気はしている。

MVCにおけるMの定義が2000年代後半以降とても難しくなってきていると思っている。それ以前は多少ユーザにとって適切でないUIやUXのシステムを出していても対して問題にならなかったんだけど、最近はその辺が如実に競争力に影響するというのと、いい加減なUXを声高に批判する鉞を持ったUX屋が増えてきた(という私の被害妄想かもしれないが)というのがあって、その辺の要求にこたえるためにユーザ一人あたりについてシステム内部とUIのやり取りが増える傾向にあり、システム内部のやり取りが増えるということはそれに従って性能要求が厳しくなる傾向にあると思っている。 こういった状況下で、ストレージから必要最小限の情報を取得したい、とか、最小パスである項目だけを更新したいといった要求がある一方で、一括である程度の情報を登録したいといった要求も依然ある。これらを両立したいと考えた場合、クラスベースでテーブルに自動的にマッピングするようなORMだといずれの場合においても非常に無駄が多くなることは少し考えればわかるだろう。巨大で堅牢なモデル設計では性能的なオーバーヘッドが多きくなりすぎるために要求に応えることが難しくなり、モデルを細切れに刻む(あるいは細切れのメソッドが大量につく)しかないのではという気がしている。そうやってコードを増やすのと、あきらめてSQLを手書きし、処理のパスを最小限に抑えることを比較すると、もしかして後者のほうがやりたいことについて率直に書ける、つまり分わかりやすいのではないか。 もちろん操作系全体で矛盾の無いことを証明できなければならないが。

ここ10年位の経験で、結局多くのWebシステムはただDBに書いては読むだけなのだから、DBに近いレベルでの堅牢を維持するのはともかくとして、レイヤーの高い部分であれこれやるのは大がかりすぎ(本来別に無駄というほどではないのだが、より性能を活かす方法がある)で、もっとマシンリソース的に効果的な設計にするべきなのでは、と思い始めている。

ベストプラクティスの庭

開発の現場でよく手順書というのがある。

たとえば、あるプロダクトを開発するための環境を作るために、やれあのソフトをインストールしろ、環境変数を追加しろ、設定値はこう、なんて書かれてあるやつだ。往々にしてこの手の文書というのはメンテナンスが後手に回りやすいので、新参のメンバーなんかがこの手順書に従って構築をやろうとすると現在ではもう古くなっている点が原因で手順通りに進まなかったりする。

最近はInfrastructure As Codeの文脈から、そういうものもコードにしておくべきではという話になってはいるものの、周辺プロダクトは勝手にアップデートしていくし、それに追随しないわけにもいかないので、手順の自動テストでも実装していれば早急に気付けるだろうが、まあ後手に回るのは変わらない。

結構この手の「後手に回る」のは過去に問題視されていて、新参、つまり一般的な会社においては新入社員などがこの立場にあたる場合が多いのだが、彼らが手順のうまくいかない部分に躓いて「手順書の更新は新入社員の仕事ではない」みたいなことを言い出すトピックがあったりした。

まあ結局はこういうのはコストの問題で、心理的・経済的そのほかいろいろな面で、コストが極端に低ければ後手後手だろうとかまわないんだろうし、逆に低コストの解決策があればみんなそれに乗っかるんだろうけど、実際のところは必ずしもそうでもないので・・・まあ難しい。

今回の話はその話ではない。

この間ある目的があって、特殊でもないがある特徴のある環境を構築する必要が出てきた。で、これにはちゃんと手順書があって、この通りやれば大体問題はない。もちろん上述の問題はあるので、現時点でのやり方に読み替えながら進める必要はあった。 そのなかで、うっかりある一つの手順を飛ばしてしまった。それが原因であとあと問題になるのだが、いつも手順書に沿って構築をおこなっているためこの手順を行わないことでどういった問題がおこるのか、自分自身よく理解できていなかった。つまり、問題が起きているのに、それがある手順が足りないためだという因果関係に気づくことができなかった。まあちょっとよく知った人に聞けばすぐわかるので良いといえばいいのだが、何にせよこの手順に落とし込めるほど知られた問題の因果を、自分が知らなかったという点に若干ショックを受けた。

これとは違うが、似たような話として、Java Puzzlers で嵌った話を過去に書いている。

09.hatenadiary.jp

過去にこういう設計の仕方はよくないであるとか、こういう場合はこういう設計にすべきであるということ(いわゆるベストプラクティスとか、デザインパターンとか、Good Partsとか言われるもの)はいろいろ学んできたつもりでいるし、世のエンジニア諸兄においてもそうあるものと信じているが、逆に考えると、「すべきでない(と思う)設計」でコードを書くことはないため、(意識的であれ、無意識的であれ)それをした時に何が起こるのかは想像の域でしかわからないのではないか。

ベストプラクティスの庭に閉じこもって振る舞う分にはなにも申し分がない。しかしひとたび踏み外した途端に一歩も進めなくなってしまう可能性がある。さらに可能性の話をするならば、使ってみたことのない設計パターンの中には目の覚めるようなイノベーションが眠ってさえいるのかもしれない。理想の庭園に閉じこもっている限り、そうした発見もないことになる。

すべきでない設計のコードを書いてみるべきか?と聞いてYesと答える人は、まあいるとすれば多分プログラミング入門書の執筆者くらいか。一般にはそんなことをしている暇はないだろう。ただそれによって問題が発生した時の対応力に大きく差が出るのであれば、一考の余地があるし、そういった技能を持つことに明確なスキルセットを認めるべきのように思う。個人的な感覚では、あまりそんなことを明確にやらせている現場はないように思うし、息まいてそんなことを推奨しだしたら煙たがられそうだ。

いや、だからと言って勧んでプロジェクトを炎上させろと言いたいわけではないのだけど。