僕がSIerを辞めた理由

今さら感が強いですが、去年の5月にSIerを辞めました。 その前の会社とあわせて6年ほど(途中6ヶ月ほど育休期間があります)受託開発がメインのエンジニアをやってきましたが、いろいろ思うところがあって受託開発ではないところに転職しました。 その理由を差し障りのない範囲で残しておこうと思います。

本題に入る前に

あらかじめいくつか断っておくと、 SIerと言ってもよく言われるような多重請負構造の中でやっていたわけではないので、大手SIerのやり方とか下請け業者の苦悩とかはネットで見る程度の知識しかありません。 この6年間は幸いにもほぼ直受け案件でしたので中間搾取もなく給与水準が著しく低かったというようなことはないです。それから、勤務場所も自社内だったので客先に打ち合わせに行く場合を除いてスーツを着ることもないですし、勤務時間や有休取得についても結構自由はありました。 開発環境についても低スペックマシンを押し付けられていた、ということもないです

理由一つ目

さて本題です。 理由の一つ目は、技術力という観点です。

Google検索で"SIer 技術力"と入れると、"低い"と候補が表示されるように、散々言われてきていることかと思います。 よく言われることとして、プログラマの能力が低い(そもそもない)だとか、使っている技術が古いだとかあります。プログラマの能力云々については僕なんかが評価できる話ではないですが、向上心のないのだけは嫌ですね。

ただ、これは転職を考え出した時点から、情報収集、実際の転職活動を経るにつれてだんだん薄れていきました。

技術力なんて受託開発かどうかにかかわらず高いところは高いし、低いところは低い。今になって客観的に見てみると前職が技術力が低いとは思わないですし、昔に在籍していた受託開発ではないベンチャーの技術力にも疑わしいところがたくさんあります。

とはいえ、技術力が高い会社には技術力の高い人が入りたがる、というのはあると思いますので、そういう意味で技術的な成果をオープンにしやすい自社開発をしている会社の方がそういう人が集まりやすい、というのはあると思います。

逆に、転職活動中に言われて「それも一理あるな」と思った点としては、受託開発だとプロジェクトごとに違う技術に触れられるが自社開発だと同じ技術と長い間付き合うことになる、という意見です。ただ、今回の場合に限ってはほぼ自社フレームワークという枠の中に縛られて開発しておりプロジェクトを移っても目新しいことはほとんどなかったですし、最後の悪あがきでJAX-RSとかAngularJSとかねじ込んでみたくらいです。そうではなくて、プロジェクトによって環境が全く異なるという場合を想像してみても、必ずしも新しい技術に触れられるというわけではなく、次のプロジェクトがCVS管理(極端ですが)とか発狂しちゃいます。

2つ目にして最大の理由

端的に言うと、受託開発という形態に対して疑問を感じてきた、ということです。

まずは

プロジェクトにおいて発注側と受注側で思惑が違うということです。 表面上は、「一緒にプロジェクトを成功させましょう」と言っておきながら、発注側としては「お金を払うんだから、できる限りのことはやれ(要件の追加や変更であっても)」と思っていますし、受注側は「工数が増えるような追加や変更は受け入れたくないし、余計な手間はかけたくない、追加でお金出してくれるならやるよ」と思っています。 入札案件で、新規開発時は低価格で受注しておいて、保守案件時に随意契約をいいことに高額な見積もりを出す、といったようなこともよく聞きましたが、つまりは受注側の思惑はそういうことです。

例えば、機能の変更案が出たとして、それが元々のスコープに含まれるかどうかとか、追加の工数がいくらだとか、そういう交渉が必要になり変更が遅れたりそもそも行われなかったりすることで、コンシューマ向けにサービスを提供する場合はそのサービスが成功するかどうかにも大きく影響する可能性もありますし、業務システムの場合にも効率化が遅れたりできなかったりということになりえます。 もちろん、そこを犠牲にしてでも凝ったUIを作ったりプログラムの品質を高めるために労力を惜しまなかったりする人もいるのですが、それが会社やチーム全体の意思とはなりにくく、結局予算重視となってしまいがちです。

次に

納品すれば終わり、といった風潮もあります。これによって、発注側としては費用面から追加開発に躊躇することになりますし、受注側としては余計なコストをかけられないため、チームを解散して場合によっては運用チームに渡したり、他の案件の間に細々と担当を続けるということもあります。本来システムというのは使われるものであればあるほど外部要因や内部要因によって絶え間なく変更や改善が必要となると思っていますので、これと相反するような納品の形はシステムの価値を下げると思います。「納品のない受託開発」といった話もありますが、それができるのはごく限られた場合のみかなという印象です。

結局

僕がエンジニアとしてどういうことに幸せを感じるのかを考えてみると、難しいこと(※無謀なことではない)や新しいことにチャレンジすることだったり、新しい知識を得ることだったり、いいと思うものをユーザに届けられたり、ユーザ(※発注者とは限らない)に喜んでもらえたり、ということです。 前者2つについては受託開発をメインとしているかどうかでそれほど大差がない気もしますが、それでも受託開発の上記の性質を考えると実績のあるものがより重視されやすいため新しいことに手を出しにくいというのはあると思います。 後者2つについては受託開発は単純に厳しいという印象しかないです。

他にも

発注者は開発会社に多くを求めすぎ。

大手SIerでちゃんとしたところであれば、何から何までやってくれるんでしょうけど、中小SIerの場合はそういう工数を犠牲にしてコストを下げる代わりに他のところを柔軟に対応することで優位性を出していると思っているんで、多くを求めすぎてはいけないと思います。例えば、業務ヒアリングも1から10まではできませんし、リプレースで移行元の仕様書ソースコードもないのに完全再現はできません。そういう話を理解してもらうのも大変。

開発規模が大きくなりすぎ

受託開発の中でも業務システムのようなものになると規模が大きくなりがちです。 一般的に、規模が大きくなりすぎると先の見通しも立ちにくくなるためプロジェクト管理は困難になり、プロジェクトを分割することでリスクを小さくすることも可能ですが、以下のような理由により規模は大きくなってしまいます。

  • 分けると社内決裁等の調整が手間すぎるから(発注者都合)
  • 上記理由もあって、本来必要かどうかわからないものまで最初から含めたがる
  • まとめて受注して人員確保や売上見込みを立てたい(受注者都合)

そんなわけで

あまりうまくまとまってはないですが、 SIerを辞めました

JAX-RS(JavaのREST API)とAngularJSでGithubっぽいWebアプリを作った

動機

今年度に入ったあたりからJerseyを仕事でも使いだしたのですが、プライベートでも試すだけじゃなくてなにか作ってみようと思うようになりました。で、公開して何かしらの反応が欲しいなと。それはプラスの反応だけでなくマイナスの反応でも、成長の糧としたいから。
あとは、モチベーションを保つための手段として。

作るものはなかなかアイデアが浮かばなくて、そのとき気になりだしてたtakezoe/gitbucket · GitHubを参考にして同じようなものを作ろうと決めました(仕様に困った時に頼れるので)。

作ったもの

Githubに公開しました。
kamegu/git-webapp · GitHub
名前は特に考えず、当たり障りのない名前にしてます。 まだ実用に堪えうるレベルではありませんが、新年という区切りのいいタイミングなので公開しました。

これをcloneして、MavenでbuildしてできたwarをTomcatで動かしてもらえればhttp://localhost:8080/git-webapp/とかでアクセスできると思います。初期アカウントとしてadmin/adminでログイン可能です。
手元ではJava8とTomcat7で動作確認してます。他のServletコンテナやJavaEEサーバでは確認できてませんが、少なくともServlet3.0以降に対応したサーバが必要です。あとはStream APIとかも使ってるのでJava8は必須です。
なお、gitレポジトリのアドレスは http://localhost:8080/git-webapp/git/ユーザ名/リポジトリ名.gitになります(その辺の細かいガイドがまだコーディングできていない。。。)

設定

基本的にはTomcat7を使ってもらえれば、特に設定をしなくても動くはずです。

~userディレクトリに.gitapp/reposディレクトリが作成され、その中にユーザ名ごとに.gitのbareリポジトリが作成されます。当然ながらこのgitリポジトリはポータブルです。
DBはデフォルトだとH2が利用され、データファイルは.gitappディレクトリ内に作成されます。
なので、デフォルトのまま起動した後に、まっさらな状態に戻したいとなった場合は.gitappディレクトリの中身を全部消したうえでTomcatを起動し直してもらえれば十分です。

H2以外にもMySQLを利用することもでき、その場合はMySQLデータベースを別途作成いただき(テーブル作成は不要です、初期化時に自動生成されます)、JDBCリソースを登録(名前はjdbc/gitapp)してもらう必要があります。Tomcatの場合、src/main/webapp/META-INF/context.xmlをcontext_mysql.xmlを参考に書き換えてもらえば大丈夫かと思います。ホスト名やユーザ、パスワード等は環境に合わせてください。

最後に

改めて書きますが、基本的な機能は動くと思いますが細かいところでまだ実用に堪えれないと思います。それでも試してもらえるのであればものすごくありがたいですし、試してもらえなくとも、コードを見てもらって何かしらのフィードバックをもらえると喜びます。

また、このプログラム内で工夫した点だったりメモしておきたいものを今後ブログに残して行く予定です。

JPQL(EclipseLink)でパラメータをbindする/しないの話

今、EclipseLinkでJPQL使って開発してます。
SQLのログを出しているとJPQLに埋め込んだ定数もパラメータ化されているのがちょっと気持ち悪いなと思ってたんですが、動作には問題なかったので、特に気にしてませんでした。

エラーとなる場合もある

ところが、どうもうまくいかない場合もあるようです。

続・JPQLでハマった話 - Java EE 事始め!

このブログでは起こった問題と回避策が紹介されています。
ここで紹介されているようにpersistence.xml

  <property name="eclipselink.logging.level.sql" value="FINE"/>
  <property name="eclipselink.logging.parameters" value="true"/>
  <property name="eclipselink.jdbc.bind-parameters" value="false"/>

とすると、確かに値がSQLに埋め込まれた状態で発行されるようです。
これは、手元でSQLの実行結果を確認するのも楽ですね。

気になること

ただ、この設定をするとsetParameterメソッドでセットした値もSQLに埋め込まれてしまうため注意が必要です。
多くの場合は問題ないと思いますが、ちょっと気になった点があるので一応書いておきます。

Oracle等の一部のRDBMSではクエリの実行計画をキャッシュとして保持します。
SQLプレースホルダをセットして値が変わるところだけをパラメータ化しておけば値が変わってもこのキャッシュが効きますが、埋め込まれてしまうとキャッシュが効きません。
パラメータ化クエリ | Use The Index, Luke!

実際にこのキャッシュが効かなくなることでDBが重くなるようなことはほとんどないとは思いますが、パラメータ化するかどうかはJPQL毎に設定する、というのもアリかと思います。
先のブログ内にあるリンクを見るとクエリごとにHintとして設定できるようです。
jdbc.bind-parameters | EclipseLink 2.5.x Java Persistence API (JPA) Extensions Reference

query.setHint(QueryHints.BIND_PARAMETERS, HintValues.TRUE);
//もしくは
@QueryHint(name=QueryHints.BIND_PARAMETERS, value=HintValues.TRUE);

なので、persistence.xmlではtrue(デフォルト)にしておいて、エラーになるところだけHintでfalseを設定するか、persistence.xmlではfalseにしておいて実効計画のキャッシュを効かせたいところだけHintをtrueにする、って設定もアリかな、と思いました。

JUnit4.12がリリースされたのでcontext-nestを試してみた

8月にこの記事で知ったJUnit4.12ですが、先日betaが取れて正式にリリースされたようなので、おいしいとうわさのcontext-nestを試してみました。

まず、pom.xmljunitのバージョンを4.12に上げます

  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>


最初、何も考えず、こんな感じでつくりました。

public class HogeTest {
    private String hoge = "hoge";

    @Test
    public void test() {
        System.out.println(hoge);
    }

    public class HogeChild1 {
        @Test
        public void test1() {
            System.out.println(hoge);
        }
    }

    public class HogeChild2 {
        @Test
        public void test2() {
            System.out.println(hoge);
        }
    }
}

testメソッドしか実行されません。
もう一度読み返すと、テストランナーが必要とのこと。
で、テストランナーってなに使えばいいの?もしくはどうやって作ればいいの?と思っていろいろみてたらリリースノートにヒントが。

junit/ReleaseNotes4.12.md at master · junit-team/junit · GitHub

One example of a runner that makes use of this extension is the Hierarchical Context Runner (see https://github.com/bechte/junit-hierarchicalcontextrunner/wiki).

というわけでこれを使いました。

pom.xmlに追加します
現時点では4.11.3と4.12.0-beta-1しかありません。
ひとまず後者を使います。

  <dependency>
    <groupId>de.bechte.junit</groupId>
    <artifactId>junit-hierarchicalcontextrunner</artifactId>
    <version>4.12.0-beta-1</version>
    <scope>test</scope>
  </dependency>

これで上記のHogeTestクラスに@RunWith(HierarchicalContextRunner.class)
をつけると無事内部クラスのメソッドも実行されました。

Glassfish4.0.1build5とjersey2.8リリース

Glassfish4.0.1 build4でいろいろ試していましたが、build5が知らないうちにリリースされてました。
http://dlc.sun.com.edgesuite.net/glassfish/4.0.1/promoted/ (2014/5/5)


よく使ってるものでいえばjerseyが2.5.1から2.8にバージョンアップしてました。
https://svn.java.net/svn/glassfish~svn/tags/4.0.1-b05/nucleus/pom.xml
これで文字コードの問題は標準でサポートされるようになってるはずです。
https://java.net/jira/browse/JERSEY-2188


hk2も2.2.0-b26から2.3.0-b02にバージョンアップされてます。
これでこの問題も解決されているはず。
https://java.net/jira/browse/GLASSFISH-20972


また試してみよう

20140510追記: 動作確認できました

jersey-mvc-freemarkerでfreemarkerの設定をカスタマイズ

freemarkerという結構いろんなことが出来るJavaのテンプレートエンジンがあります。
http://freemarker.org/
今悪い意味で話題のstruts2でも使われているライブラリです。
※ freemarker自体が悪いわけではないです

また、今JavaのWAFでちょっと話題のJAX-RSという仕様がありこの参照実装であるJerseyというライブラリがあります。
https://jersey.java.net/
現時点で2.7です。

JAX-RSはRESTful Web Serviceのための言語仕様ですが、Jerseyではその独自拡張としてMVCのための機能をもっています。
その機能を使えば好きなテンプレートエンジンを使うように出来るんですが、freemarkerについては最初から拡張に含まれています。
https://jersey.java.net/documentation/latest/mvc.html#d0e13644


ここからが本題ですが、
freemarkerにはいろいろ設定が出来るのですが、
jersey-mvc-freemarkerをそのまま使うと簡単に設定が出来ません。
http://freemarker.org/docs/pgui_config.html

そこでjersey-mvcが用意している拡張ポイントを使います。
デフォルトでは下記のようにfreemarker.template.Configurationインスタンスが作られます。

                final List<TemplateLoader> loaders = Lists.newArrayList();
                if (servletContext != null) {
                    loaders.add(new WebappTemplateLoader(servletContext));
                }
                loaders.add(new ClassTemplateLoader(FreemarkerViewProcessor.class, "/"));
                try {
                    loaders.add(new FileTemplateLoader(new File("/")));
                } catch (IOException e) {
                    // NOOP
                }

                // Create Factory.
                final Configuration configuration = new Configuration();
                configuration.setTemplateLoader(new MultiTemplateLoader(loaders.toArray(new TemplateLoader[loaders.size()])));
                return configuration;

github

このConfigurationインスタンスにいろいろ設定を加えたいのですが、そこで、この親クラスを見てみます。
github
すると、getTemplateObjectFactoryメソッド内(1行目)で、jerseyのconfigクラスに設定されたプロパティを見ていることがわかります。
ここでのプロパティ名は
MvcFeature.TEMPLATE_OBJECT_FACTORY + suffix = FreemarkerMvcFeature.TEMPLATE_OBJECT_FACTORY
ですので、
ResourceConfigの具象クラスで次のように設定すればOKです。

property(FreemarkerMvcFeature.TEMPLATE_OBJECT_FACTORY, value);

valueには、Configurationクラスまたは継承先クラスのインスタンスもしくはパッケージ名もしくはクラスを指定できます。
パッケージ名もしくはクラスを指定した場合はインスタンスの作成にはhk2のserviceLocatorが使用されます。これによりDIが可能になります。

ただし、今回はパラメータにServletContextを渡したいのでインスタンス化したものを渡すのは難しそうです。
ということでfreemarker.template.Configurationクラスを継承したクラスを作ってそのコンストラクタ内でFreemarkerViewProcessor内の処理を実行させるて、その上で設定を行うのが良さそうです。

ということで、作りました。
FlexibleConfiguration
GitHubリポジトリ
今回は別途Mapで設定パラメータもResourceConfigに設定できるようにしてそれを読み込むことで柔軟に設定できるようにしました。

githubにmaven repositoryを作成して自作ライブラリを登録する

はじめに

Mavenのライブラリは基本的にはCentral Repositoryに登録されていますが、手続きも面倒そうだったりするのでもっと手軽に公開サーバに登録したいということでやってみました。

このあたりを参考にしました。
http://blog.lampetty.net/blog_ja/index.php/archives/527
この中ではGitHub Maven Pluginsを使ってます。
https://github.com/github/maven-plugins
githubにはGitHub Pagesというのがあって通常のWebサイトのように使うことが出来ます。この機能を利用します。

上の参考リンクからちょっと変えた点

  • distributionManagementをbuild.directory内にするとmvn cleanしたときに消えて、それをpushすると古いバージョンのjarが個人用公開リポジトリからも削除される
  • GitHub Pagesを使うように変更
  • source-jarを作るように変更

シンプルな方法

GitHub Maven Pluginsを使わずに、distributionManagementのurlを任意のディレクトリにしてmvn deployすればjar等々が作成される。それをGitHubMavenリポジトリと同期させればOK

基本的にはこれだけでいいはず。

    <properties>
        <internal.repo.directory>C:\mvn-repo</internal.repo.directory>
    </properties>
    <distributionManagement>
        <repository>
            <id>internal.repo</id>
            <name>Temporary Staging Repository</name>
            <url>file://${internal.repo.directory}</url>
        </repository>
    </distributionManagement>    

これでC:\mvn-repoディレクトリでgit initしてgit remote addでGitHubと同期できるはず。
gh-pagesブランチにpushすればGitHub Pagesで公開される。

これで公開したライブラリを依存関係に含めたい場合はdependencyの登録の前にリポジトリ追加を行う。

<repository>
    <id>kamegu-github</id>
    <url>http://kamegu.github.io/mvn-repo/</url>
</repository>

これは下記のようなraw.github.comを使用するよりシンプルかと思います。

<repository>
    <id>kamegu-github</id>
    <url>https://raw.github.com/kamegu/mvn-repo/master/</url>
</repository>

source-jarもつくるなら、maven-source-pluginを使用する。

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.2.1</version>
                <executions>
                    <execution>
                    <phase>package</phase>
                      <goals>
                        <goal>jar</goal>
                      </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

GitHub Maven Pluginを使用する

上の参考リンクのようdistributionManagementをbuild.directory内にするとmvn cleanしたときに消えてしまいます。
そうすると、GitHubへpushした際に古いバージョンのjarファイルが消えてしまっていました。
それじゃダメだと思ってたんですが、Pluginにmergeオプションが設定できるようで、これをtrueにすれば解決できるようです。

なので、pom.xmlを最終的にこのようにしました。
これとは別にsettings.xmlも設定しています。

    <properties>
        <github.global.server>github-kamegu</github.global.server>
    </properties>
    <distributionManagement>
        <repository>1
            <id>internal.repo</id>
            <name>Temporary Staging Repository</name>
            <url>file://${project.build.directory}/mvn-repo</url>
        </repository>
    </distributionManagement>    
    <build>
        <plugins>
            <plugin>
                <groupId>com.github.github</groupId>
                <artifactId>site-maven-plugin</artifactId>
                <version>0.9</version>
                <configuration>
                    <merge>true</merge>
                    <message>Maven artifacts for ${project.version}</message>
                    <!-- Jekyllという静的ページを生成するツールは無効 -->
                    <noJekyll>true</noJekyll>
                    <outputDirectory>${project.build.directory}/mvn-repo</outputDirectory>
                    <includes>
                        <include>**/*</include>
                    </includes>
                    <!-- GitHubリポジトリ名 -->
                    <repositoryName>mvn-repo</repositoryName>
                    <!-- GitHubユーザ名  -->
                    <repositoryOwner>kamegu</repositoryOwner>
                </configuration>
                <executions>
                    <!-- run site-maven-plugin's 'site' target as part of the build's normal 'deploy' phase -->
                    <execution>
                        <goals>
                            <goal>site</goal>
                        </goals>
                        <phase>deploy</phase>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

以上