tokuhirom's blog

[java]インターフェースにArrayを露出させた場合の話

Java の世界ではプリミティブな配列が存在している。 プリミティブな配列は速度が List などと比べると圧倒的に速いのでついつい使いたくなる。

しかし、Java 言語では immutable な配列を作ることができないので、内部で配列を保持している場合、それをそのまま返却することはできない。

つまり、以下のようなクラスはよくない。Foo(o).getO() で取得された配列を操作すると、Foo を作成した時点で作成された o を変更してしまうからだ。

public class Foo {
    private final Object[] o;
    public Foo(Object[] o) {
        this.o = o;
    }
    public Object[] getO() {
        return this.o;
    }
}

よって、汎用的なライブラリでは以下のようにしたほうがベター。

public class Foo {
    private final Object[] o;
    public Foo(Object[] o) {
        this.o = o;
    }
    public Object[] getO() {
        return this.o.clone();
    }
}

しかしこれでは、配列のコピーが発生してしまって、データが大きい場合に問題になる。 そこで、配列をあきらめて List を使う。

public class Foo {
    private final List<Object> o;
    public Foo(final List<Object> o) {
        this.o = Collections.unmodifieableList(o);
    }
    public List<Object> getO() {
        return this.o;
    }
}

これで、安全になった。 Collections.unmodifieableList を通していることにより、内部データを操作される心配はもはやない(Reflection とか使われたらアレだけど、それは自己責任)。

まとめ

Java ではオブジェクトの外部とのやりとりに配列を使うのはできるだけ避けたほうがよいケースがある。 (内部ではパフォーマンスを出すために使ったほうがいいケースも多々ある)

Created: 2014-09-22 10:34:56
Updated: 2014-09-22 10:34:56

Java なウェブアプリのテストをするための mech というライブラリを書いていた

https://github.com/tokuhirom/mech

@Test
public void testGoogle() throws Exception {
    try (Mech mech = new Mech("http://google.com/")) {
        try (MechResponse res = mech.get("/").execute()) {
            assertEquals(200, res.getStatusCode());
        }
    }
}

こんな感じで使える。この使い方は本筋じゃなくて本当にやりたいのはこっち。

class MyServlet extends HttpServlet {
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.getWriter().write("Hello");
    }

}

public class ServletTest {
    @Test
    public void test() throws Exception {
        try (MechJettyServlet mech = new MechJettyServlet(new MyServlet())) {
            try (MechResponse res = mech.get("/").execute()) {
                assertEquals(200, res.getStatusCode());
                assertEquals("Hello", res.getContentString());
            }
        }
    }
}

適当につくったサーブレットを、ささっとテストできる。裏のスレッドで Jetty たてて、それベースでテストしてくれる。 便利~。

なかなか便利に使えるようになったんで、オープン&シェアさせていただきます。

JSON をポストしたい

try (MechResponse res = mech.postJSON("/json", form).execute()) {
    assertEquals(200, res.getStatusCode());
    assertEquals("+++{\n"
            + "  \"name\" : \"hoge\"\n"
            + "}+++", res.getContentString());
}

JSON を受け取ったのを読み込みたい

Form form = res.readJSON(new TypeReference<Form>() {
});

Jackson でいい感じに処理できます。

POST したい

try (MechResponse res = mech.post("/postForm").param("name", "pp太郎")
        .execute()) {
    assertEquals(200, res.getStatusCode());
    assertEquals("pp太郎", res.getContentString());
}

multipart/form-data でテストしたい

try (MechResponse res = mech.postMultipart("/postMultipart")
        .param("name", "pp太郎").file("file", new File("pom.xml"))
        .execute()) {
    assertEquals(200, res.getStatusCode());
    assertEquals("pp太郎XXXpom.xml", res.getContentString());
}

リクエストとレスポンスの具合を表示したい

mech.addRequestListener(new PrintRequestListener(ps));

うまいこと、全部経過を表示できる。これでテストした結果を、ドキュメントに貼ったりしてる。

Installation

<repositories>
    <repository>
        <id>tokuhirom</id>
        <url>https://tokuhirom.github.io/maven/releases/</url>
    </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>me.geso</groupId>
        <artifactId>mech</artifactId>
        <version>0.7.1</version>
    </dependency>
</dependencies>

pom.xml にこういう感じで書いてくだされ。

javadoc

http://tokuhirom.github.io/maven/javadoc/me/geso/mech/0.7.1/apidocs/index.html

このへんにおいてあります。

Created: 2014-09-21 18:59:13
Updated: 2014-09-21 18:59:13

Eclipse が起動しなくなった

なんかしらんけど起動しなくなった。Eclipse が起動しなくては仕事にならないということで調べたところ、Windows 上での情報が多くて辛かった。結局、以下のコマンドでかいけつ。

rmtrash ~/Documents/workspace/.metadata/.plugins/org.eclipse.e4.workbench/

http://stackoverflow.com/questions/11810102/eclipse-juno-startup-error-log-file

Created: 2014-09-16 13:56:27
Updated: 2014-09-16 13:56:27

Java で lazy なアクセサを書きたい

http://projectlombok.org/features/GetterLazy.html

それ lombok でできるよ。

import lombok.Getter;

public class GetterLazyExample {
    @Getter(lazy=true) private final double[] cached = expensive();

    private double[] expensive() {
        double[] result = new double[1000000];
        for (int i = 0; i < result.length; i++) {
            result[i] = Math.asin(i);
        }
        return result;
    }
}

とか書くと

public class GetterLazyExample {
    private final java.util.concurrent.AtomicReference<java.lang.Object> cached = new java.util.concurrent.AtomicReference<java.lang.Object>();

    public double[] getCached() {
        java.lang.Object value = this.cached.get();
        if (value == null) {
            synchronized(this.cached) {
                value = this.cached.get();
                if (value == null) {
                    final double[] actualValue = expensive();
                    value = actualValue == null ? this.cached : actualValue;
                    this.cached.set(value);
                }
            }
        }
        return (double[])(value == this.cached ? null : value);
    }

    private double[] expensive() {
        double[] result = new double[1000000];
        for (int i = 0; i < result.length; i++) {
            result[i] = Math.asin(i);
        }
        return result;
    }
}

となる、と。。

めっちゃ便利だな!

Created: 2014-09-12 17:14:26
Updated: 2014-09-12 17:14:26

Java 8 Stream API で Immutable な List を作成する

data.stream().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))

とすればいい。

Collectors.collectingAndThen を使えば、collect した後の結果をさらに加工することができる。 Collections::unmodifiableList をコールすることにより変更できない List を作成することが可能。

冗長だけど……

Created: 2014-09-12 09:03:01
Updated: 2014-09-12 09:03:01

YAPC::Asia 2014 で Test::Kantan というライブラリについて発表してきました

ご査収ください。

今回のカンファレンスでは Test::Ika は ikasam_a さんの好きにしていいですよ、という話ができたのが良かったですね。

最近、Java を主に書いてて、 Perl のライブラリとかあんまメンテナンスしきれてないのもあるんで、というか review と releng はしてるんですけど、誰かメンテナンスしたい人いたらメンテナンスお願いしたい情勢です。

Created: 2014-09-10 19:00:11
Updated: 2014-09-10 19:00:11

How do I write pretty print JSON filter with Jackson?

byte [] bytes = ...;
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
JsonNode tree = mapper.readTree(bytes);
bytes = mapper.writeValueAsBytes(tree);

This code works well.

Created: 2014-09-09 18:28:27
Updated: 2014-09-09 18:28:27

Perl 初心者がウェブアプリケーションを書く時に気をつけるべきこと

$c->req->param('id')

みたいなメソッドは使ってはいけない。これは歴史的経緯から残っているものなので、基本的に使わない方がいい。

$c->req->parameters->{id}

をかわりに使ってください。

Perl の世界には List コンテキストというものがあって、これがウェブアプリケーションを開発するときには鬼門となります。

+{ id => $c->req->param('id') }

のようなコードは、param メソッドはリストコンテキストではすべての id を返すので、

?id=3&id=hasegawa&id=yosuke

というようなクエリが来ている場合、

+{ id => 3, hasegawa => 'yosuke' }

のようなデータ構造が作成される。これは明らかに意図していない挙動である。

以下の様にかくのがおすすめです。

my $id = $c->req->param('id')
    or die "id NAIYO!";
+{ id => $id }
Created: 2014-09-04 12:58:35
Updated: 2014-09-04 12:58:35

pom.xml から classpath を生成するやつ(cache 機能つき)

pom.xml で指定した依存関係を読み込んだ状態で、いい感じに起動させたい、みたいなことがよくある。しかし、都度 maven から起動するのもめんどくさいし、mvn exec:java はさまるのもだるい。groovy を起動するときにも、-cp を細かく指定するのはだるい。

というわけで、そういうのを解決するソリューションとして mvngencpmvnsavecp という2つのコマンドが知られている。

が、どうも mvngencp と mvnsavecp が手元の環境でうまく動かなくて、治すのもめんどくさかったので ruby でゴリゴリっと書いてやった。

http://github.com/tokuhirom/mvnclasspath/blob/master/mvnclasspath


#!/usr/bin/env ruby

require 'uri'
require 'fileutils'

class MvnClassPath
  def initialize
    cacheKey = URI.escape(File.absolute_path(Dir.pwd))
    @cachepath = "#{Dir.home}/.mvnclasspath/#{cacheKey}"
  end

  def getClassPath
    if File.exists?('pom.xml')
    # generate cache file path.
      if File.exists?(@cachepath)
        if File.mtime(@cachepath) >= File.mtime('pom.xml')
          # cache hit.
          return IO.read(@cachepath)
        else
          return generateByMaven()
        end
      else
        return generateByMaven()
      end
    else
      return '.'
    end
  end

  def generateByMaven()
    cp = `mvn dependency:build-classpath -DincludeScope=test | grep -A 1 'Dependencies classpath:'`.split(/\n/)[-1]
    FileUtils.mkdir_p(File.dirname(@cachepath))
    File.write(@cachepath, cp)
    return cp
  end
end

puts MvnClassPath.new.getClassPath()

これをパスの通ったイチにおいておけば、以下のようにして、簡単にクラスパスつきでプログラムを実行できる。

groovy -cp `mvnclasspath` hoge.groovy
Created: 2014-09-03 16:27:31
Updated: 2014-09-03 16:27:31

なぜ Teng は良いものなのか

なぜ Teng は良いものなのか、を YAPC で再考させられたのでここにメモしておく。

Teng は自社開発のウェブアプリケーションを作ってる人たちが作っていて、それがうちのニーズにあってるのでいいっていう話であって、どこでもすごい最高!! と主張したいわけではないです。まあ、個人の感想ですね。

ソースが読みやすい

ソースがよくモジュール化されていて、読みやすい。自身で書いている部分が多いという贔屓目を抜きにしても読みやすいんじゃないかなーと。

僕らのような自社開発のウェブ屋では、なにか無茶な要望を受けた時にささっと対応するということが求められるシーンが多いので、ソースの読みやすさというのはかなり重要なファクターとなっています。

複雑な SQL を発行できないように機能が制限されている

SQL ビルダーを使って JOIN やサブクエリを駆使したウェブアプリケーションを開発してしまうと、運用担当や後世の開発者が、以下のような問題を抱えることになります。

  • mysql のスロークエリログを元にクエリを修正しようとしても、どこで実装されているのかを調べることが困難
  • クエリビルダの文法に運用担当が慣れていないとチューニングが困難

実装箇所がわかりづらいという点については、SQL Comment で生成位置を挿入する方法もあるので、それでも解決できます。 後者の問題は深刻で、大体の場合では社内でSQL に詳しい人は運用を担当しているので、そういう人に診てもらう時にすごく嫌なかおをされたりします。

まあ、そんな感じでこのへんははある程度以上トラフィックがあるサイトでは致命的な問題となってきます。

Teng では、SQL ビルダーライブラリの機能はある程度制限したうえで、複雑なクエリは直接SQLをゴーリごりと書けばいいという感じになっています。

どこでクエリが発行されているかコードを見ればすぐわかる

過去の O/R Mapperでは、コードを見てもどこで DB にアクセスしているのかわからないものもありました。

そのような O/R Mapper を使っていると、まあ常識的に考えてチューニングが困難になります。

Relationship サポートをコアに断固としていれていない

has_ahas_many などのサポートを標準ではいれていない。

これを入れると、開発が楽になるが一方で何も考えずにクエリを発行してしまいがちになる。

まあ、has_a なり has_many にあたるメソッドは各自で生やせばいいんで、いいですね。 一息おいて、自分で考えて実装する分には問題はない。

Teng が作成された本当の理由

DBIx::Skinny っていう Teng の元になった O/R Mapper で上記のうちの大体の機能は実現されていたんですが、DBIx::Skinny ではクラスとしてもインスタンスとしても動くという謎の機構が入っていて、これのせいで非常にコードが読みづらくなっている。この問題を解消するために Teng として作りなおしたというのが大きい気がしている(これは、僕が持ってる印象なので、他のこみったの人は違う印象かもしれない)。

まとめ

Teng は初期開発の楽さよりも安定した運用を重視したハイパフォーマンスウェブアプリケーション開発者養成ギプスである。

Created: 2014-09-01 12:47:46
Updated: 2014-09-01 12:47:46