覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Javaでプログラムを書く際に意識しておきたいこと

以下、個人的にJavaでプログラムを書く際に意識しておきたいことです。

ただし、学術的な裏付けなどがある内容でありません。あくまで私の経験に由来する内容となっています。

そもそもコンテキストによってはそぐわない内容もあると思いますので、その辺はうまいことスルーしてもらえたらと思います。


Collection

空のList

メソッドの戻り値として空(size==0)のListを返却する場面がありますが、その場合はCollections.emptyListを使うのが良いです。
new ArrayList()でListを生成してreturnするよりも、処理も早くコードの意味も分かりやすくなります。
ただし、このメソッドで返されるListはImmutable(不変)であることを理解しておく必要があります。
Collectionsクラスには、空Setや空Mapを返すメソッドも用意されています。

大量データをListに格納する場合はサイズ指定

ArrayListのデフォルトコンストラクタで生成されるインスタンスのデータ格納容量は10です。
この値よりもはるかに大量のデータを格納(add)することが確定している場合は、最初からサイズ指定のコンストラクタを使うべきです。
基本サイズ(10)を起点にしてデータを格納を繰り返すと容量拡張が何度も実行されパフォーマンスに悪影響を及ぼす場合があります。
(C言語でいうところの、mallocをしたあとにreallocを繰り返しているような感じになってしまいます)

EnumMap

Enum(列挙型)をキーにしたMapを作成したい場面がありますが、そのような場合はHashMapではなくEnumMapを使用した方がよいです。
HashMapを使用するよりも実行効率が良くなります。

Vector、Hashtableを使用しない

java.util.Vectorjava.util.Hashtableは同期化されたCollectionですが、性能などの問題もあり使用しないようにするべきと一般的に言われています。
同期化が必要ない場面ではArrayListHashMapへの置き換えを行うべきです。同期化が必要な場面でも別の代替手段を検討すべきです。
よほど理由がない限りVectorHashtableを使用しない方がよいです。

サードパーティのCollectionライブラリ

Java標準のコレクションフレームワークの実装は古いと言われ、問題も抱えています。
(プリミティブ型をそのまま格納できない、メモリ効率が良くない、コレクションの種類が貧弱、、、etc)
標準のCollectionの問題等を解決するために以下のようなライブラリが存在しています(標準のCollectionでパフォーマンスで問題となった場合や使い勝手が悪い場合などに利用を検討するのがよいです)


メソッド

戻り値にnullを使用しない

return nullとなっているメソッドは、メソッド利用側でnullチェックが必要となり、チェックを忘れるとNullPointerExceptionを引き起こす場合があります。
メソッドを作成する際に出来る限りnullをreturnしないように心掛けた方が良いです。
戻り値の型がListなら空Listをreturnする。戻り値の型が配列なら要素数0の配列を返却する。それ以外の場合はNullObject(等のデフォルト動作をするオブジェクト)、Optionalの導入を検討する。
どうしてもnullをreturnする必要がある場合は、javadocコメントにどういったケースでnullがreturnされるかを記述するべきです。

引数でnullをとったら即時にエラーを通知する

一般的なほとんどのAPIにおいて引数にnullを渡されるというのは異常ケースです。(API利用側のバグと言えます)
業務の(publicな)メソッドで引数にnullをとった場合もnull用の特別の業務処理を行うのではなく、即時にNullPointerExceptionIllegalArgumentExceptionで呼び出し側にエラーを通知するようにした方が良いです。
null値をAPI間で引き回すと処理が分かりにくくなったり、バグが混入しやすくなります。
(もちろん、nullを特別に扱わなければならないケースもあるとは思いますが、そのようなケースは稀です)


マルチスレッド

スレッドセーフではない危険な標準クラス

スレッドセーフだと思って使用するとマルチスレッド環境下で思いもよらない例外が発生するクラスがjava標準で存在します。

  • java.text.SimpleDateFormat
    • 有名ですが、SimpleDateFormatはスレッドセーフではありません。staticなメンバとして保持したりすると予想外の動きをします
      対処法としては以下
      • commons-langのFastDateFormatを使用する
      • ThreadLocalにして使用する
      • 処理のたびにnewする
        • ただしSimpleDateFormatのインスタンスを生成(new)するコストはかなり高いです。
  • java.text.NumberFormat
    • NumberFormatのjavadocを読むとわかるのですがマルチスレッドでアクセスする場合は外部的に同期化する必要がある旨が記載してあります。
  • java.text.DecimalFormat
    • NumberFormatと同様です
  • java.text.MessageFormat
    • NumberFormatと同様です

スレッドセーフの表明

JSR305のアノテーションを使用することで対象クラスがスレッドセーフであるか、そうでないかを表明することができます

  • @ThreadSafe ・・・ スレッドセーフであることを表明するアノテーションです
  • @NotThreadSafe ・・・ スレッドセーフではないことを表明するアノテーションです

(上記アノテーションはこの本に由来する)


例外

例外の翻訳(exception translation)

例外をスローする側とキャッチする側のレイヤーが異なる場合などは特に、抽象概念に適した例外を投げる必要があります。
たとえば、何らかのデータにアクセスするオブジェクトが、SQLExceptionFileNotFoundExceptionIOExceptionの3つをスローするよりも
それらをラップしたDataAccessExceptionをスローする方が上位側も扱いがやりやすくなります。
(本件は「Effective Java」の項目61にも同様の内容が書かれています)

複数の例外をスローしない

「例外の翻訳」と同様ですが、1つのメソッドから複数の例外をスローするのは得策ではありません。
そのメソッドを利用する側からすると迷惑でしかありません。極力1つ(or 2つ)の意味のある例外をスローするようにすべきです。
複数の例外をスローするメソッドを利用する側は、java.lang.Exceptionでまとめて例外をcatchをしたくなってしまいます。

  • よろしくない例
// 本メソッドの呼び出し側はそれぞれの例外を個別にcatchする必要がある
public ServiceResult doService(ServiceRequest request, User user) throws NotCertifiedException, CertificateException, SQLException, SocketException {
    
    // do something
}
  • 改善例
// 本メソッドの呼び出し側は複数の例外を個別にcatchする必要がない
public ServiceResult doService(ServiceRequest request, User user) throws ServiceException {
    
    // do something
}

標準例外を使用する

外部向けのAPI等では特に以下のようなJava標準例外を積極的に使用することでバグを生みにくくなります

クラス名 利用シーン
IllegalArgumentException 引数が想定しない値である場合などにスロー
NullPointerException (引数などがnullで)null操作が発生してしまう場合などにスロー
IllegalStateException 実行すべき状態が不整合である場合などにスロー
UnsupportedOperationExcption 対象の操作を提供しない場合などにスロー
IndexOutOfBoundsException 範囲外の要素にアクセスした場合などにスロー

チェック例外 vs 実行時例外

Javaで定義する例外は、Exceptionクラスを継承したチェック例外(検査例外)とRuntimeExceptionクラスを継承した実行時例外の2つに分かれます。
チェック例外を定義した場合、例外のハンドリングをしているかどうかをコンパイラでチェックすることができます。
これは一見、例外のハンドリング漏れがなくなるので良い事のようにも思えるのですが、
メソッドで上位に例外をスローしていくような場合は、例外がスローされる通り道となってしまう中間のメソッドにおいてもthrows節に対象例外を記述する必要があり、
これは対象メソッドが関心のない例外によって汚されているとも考えられます。
また、チェック例外はJava以外の言語ではほぼ見られない機構であり、本当に必要なものかは若干不明です。
例外クラスを定義する場合に、何も考えずExceptionクラスを継承するのではなく一旦上記のような問題点等があることを考慮した方が良いです。
Javaのチェック例外という概念がJava誕生からこれまでの歴史をもってしても、今なお他言語や他プラットフォームに普及していません。
チェック例外が評価されていない(扱いにくい等の負の側面が多い)という事実だと思われます。

CloneNotSupportedException

Object#cloneメソッドがスローするCloneNotSupportedExceptionはチェック例外ですが、
スローされる原因はCloneableインタフェースの実装し忘れによるものです。そのような状況はコードとしてのバグと言ってもいいと思います。
また、この例外をスローされた側はcatchしてもほとんどの状況で有用な処理はできないはずです。
cloneメソッドをオーバーライドする場合はCloneNotSupportedExceptionを盲目的にスローするのではなく、
IllegalStateExceptionRuntimeException等の実行時例外でラップ(またはInternalErrorで例外メッセージをラップ)してスローし、 cloneメソッドの利用側の負担を減らすのが良いでしょう。
(正直、CloneNotSupportedExceptionがチェック例外である理由は基本的にないと考えられます。cloneメソッドはチェック例外をスローしているために扱いにくいAPIとなっている一例だと思います)

UnsupportedEncodingException

String#getBytes等のメソッドで本例外がスローされる場合があります。
CloneNotSupportedExceptionと同様にチェック例外となっていますが、この例外をcatchしても業務アプリケーションとして有用な処理はほぼできないと考えられます。
(そもそも、キャラセットを"UTF-8"等と指定すれば、UnsupportedEncodingExceptionはスローされません。(Javaのプラットフォーム実装は標準の文字セットをサポートするからです))
本例外をスローするメソッドはRuntimeException等にラップしてスローする事を検討すべきです。

java.lang.Exceptionをスローしない

通常の業務処理において、java.lang.Exceptionをスローすべきではありません。
java.lang.Exceptionを投げてしまうと、エラー(例外)の意味がかなりぼやけてしまいます。対象の処理やレイヤーに適応した名前の付いたExceptionをスローすべきです。
自分の作成したメソッドがjava.lang.Exceptionをスローしている場合、いったいどんな場合にその例外がスローされ、どういう意味を上位に通知しようとしているのか考え直してみるべきです。
(フレームワークやプラットフォーム的な特殊な処理においてはjava.lang.Exceptionを敢てスローするケースがあります)

java.lang.Exceptionをキャッチしない

通常の業務処理において、java.lang.Exceptionをキャッチするのは最小限にすべきです。
汎用的に例外を捕捉できるため、java.lang.Exceptionをキャッチしたくなりますが、java.lang.Exceptionをキャッチするということは、RuntimeExceptionのサブクラスもキャッチするということです。
通常の業務処理でそのようなことを意図する場面はかなり少ないはずです。
RuntimeExceptionのJavadocを見ると分かりますがJava標準でRuntimeExceptionの既知のサブクラスはかなりの種類があります。これらを敢て捕捉したくて、"catch (Exception e)"と書いていますか?
(フレームワークやプラットフォーム、呼び出し階層の上位レイヤー、外部システムとの連携を担うレイヤー等においては、java.lang.Exceptionを敢てキャッチするする役割のクラスが必要な場合もあります)

例外を握りつぶさない

呼び出したメソッドから通知された例外を握りつぶす以下のようなコード書くのは避けた方が良いです。

try{
   someMethod(); // IOExceptionをスローする可能性がある
} catch(Exception e) {
    // 何も書かないで無視する(やってはいけない!!)
}

実際に例外が発生した場合にそのエラー内容や原因が全く分からなくなってしまいエラーの解析などができなくなってしまいます。
基本的には例外に対応した何らかの処理、そのまま(またはラップして)上位に例外をスロー、最悪でも例外内容をログ出力して例外を記録した方が良いです。
以下のコードの場合はjava.lang.Exceptionをcatchして無視しているので、バグなどで発生したRuntimeException(のサブクラス)等も握りつぶしてしまい、
アプリケーションとして挙動がおかしくなってしまう可能性すらあります。
(ただし、close処理などで意図的に例外を無視する場合などの理由があるケースは除きます)

バグを意味するRuntimeExceptionをcatchしない

java標準でRuntimExceptionのサブクラスにはコードのバグによって発生するような例外(IllegalArgumentExceptionIndexOutOfBoundsException・・・etc)が存在します。
そのような例外を通常の業務処理においてキャッチして処理を続行すべきではありません。
例えば、IndexOutOfBoundsExceptionが発生するケースは、C言語ではセグメンテーション違反が発生してまう状況に近いとも言えます。
セグメンテーション違反が発生したら対象アプリケーションは続行できません。
このように考えると、バグを意味する例外を無理やり捕捉して業務処理を続行するのはかなり不自然と言えます。

例外をスローする際はエラーの原因情報を含める

稼働してるアプリケーションで例外が発生してエラーとなった際に、エラー解析を行うためにはログ(に出力されたスタックトレース)が頼りとなります。
エラーを解析する際に頼りとする情報には以下のようなものがあります。

  • 例外(例外クラス)の名前そのもの
    • 例外の名前が具体的であればエラーの原因が推測しやすくなります
  • 例外に含まれている元の原因となった例外のスタックトレース
    • 元となる例外がわかると本当のエラー発生個所が推測しやすくなります
  • エラー内容を意味するメッセージ
    • 例外の内容を補足してくれます。詳細なメッセージが含まれているとエラーの原因が分かりやすくなります。
      エラーの原因情報をできるだけ上位に通知するために、以下の方針で例外をスローした方が良いです
      • 呼び出し元のメソッドで例外が発生し、それをキャッチして新しい例外をスローする場合
        ⇒ 元の例外を新しい例外に含める(元の例外も含めて上位に通知する)
      • 業務的なチェック処理結果からエラー通知する場合
        ⇒ チェック対象の値と何のチェックでNGだったか(何がまずかったのか)をメッセージとして例外に含める
        (※ただしチェック対象の値が巨大なオブジェト(巨大な文字列etc)の場合は含め方に何らかの工夫は必要です)

実行時例外もjavadocに記述する

RuntimeExceptionを継承した例外クラスをスローするメソッドでは、 javadoc の @throwsにその例外を記載しておく必要があります。
ただし、throws節には記述しません。

誰にもcatchされなかった例外のハンドリング

UncaughtExceptionHandlerをThreadに設定することでハンドリングできます

以下はメインスレッドに例外内容をロギングするUncaughtExceptionHandlerを設定するサンプルです。

public class UncaughtExceptionHandlerSample {

    private static final Logger logger = LoggerFactory.getLogger(UncaughtExceptionHandlerSample.class);
 
    private static final UncaughtExceptionHandler UNCAUGHT_EXCEPTION_HANDLER = new UncaughtExceptionHandler() {
 
        /** 
         * キャッチできなかった場合は、とりあえずログ出力する
         */
        @Override public void uncaughtException(Thread thread, Throwable throwable) {
            logger.error("キャッチできない例外発生", throwable);
        }
 
    };
 
    public static void main(String[] args) {
        // メインスレッドにUncaughtExceptionHandlerを設定
        Thread t = Thread.currentThread();
        t.setUncaughtExceptionHandler(UNCAUGHT_EXCEPTION_HANDLER);
    }
}


日時

TimeUnit

java.util.concurrent.TimeUnit(列挙型)を使用すると時間の単位変換などをわかりやすく記述できる。

// 4時間をミリ秒単位に変換
System.out.println(TimeUnit.HOURS.toMillis(4));    // -> 14400000
 
// 1日を秒単位に変換
System.out.println(TimeUnit.DAYS.toSeconds(1));    // -> 86400
 
// 3秒スリープ
TimeUnit.SECONDS.sleep(3);

// 1分スリープ
TimeUnit.MINUTES.sleep(1);

Calendarの月の値範囲

java.util.Calendarで扱う月の値範囲は0~11です。(1~12ではありません)
(よく知られた話ですが、分かりにくさがつきまとう。利用時は注意が必要。)

Calendarのメモリ消費量

java.util.Calendarのインスタンスのメモリ消費量は700byteを超えており、普通のクラスに比べてかなりサイズが大きいです。(例えば、java.util.Dateの場合はメモリ消費量 24byte程度です)
そのため、Calendarのインスタンスをクラスのメンバに保持したり、大量に生成したりするのは避けた方が良いです。


JavaBeans

単純なJavaBeansは不整合な可能性あり

単純なgetter/setterを持つJavaBeansはオブジェクトの生成処理が各setterによって分割されているため、生成過程では不整合な状態になっている可能性があるという重大な欠点を持っています。
(特定のメンバには値が入っているが特定のメンバはnullになっていて、そのような状態がクラスとして本当は許容されない等)
クラスを不変(immutable)にすることで基本的には解決します。メンバが多い場合はBuilderパターンを取り入れることでスマートになります。
(※ただし、JavaBeansを必要とする外部ライブラリも存在しますので、そのようなライブラリを使用する場合等はJavaBeansを利用してください)


数値型

double型の正の無限・負の無限・非数

double型には正の無限大値を表すDouble.POSITIVE_INFINITY、負の無限大値を表すDouble.NEGATIVE_INFINITY、非数を表すDouble.NaNが存在します。
通常の処理で有用なのは上記の値を除いた有限値のみです。
外部システムなどの信頼性が確かでないところからdouble値を受け取ったり、文字列からdouble値を作り出す場合などは、上記の値でないことをチェックしておく必要があります。
(非数の挙動として、 Double.NaN == Double.NaN は結果がfalseとなることには注意が必要です)
(正の無限、負の無限、非数についてはfloat型にも同様の事が言えます)

BigDecimalで四捨五入が四捨五入にならない

BigDecimalをdouble型の値を使って生成すると四捨五入処理が場合により五捨六入になってしまいます。
BigDecimalを生成する場合はString型の値を指定するか、BigDecimal.valueOf(double)を使用します。

NG例:

public static BigDecimal roundNumber(double val, int scale) { 
    BigDecimal bd = new BigDecimal(val); 
    bd = bd.setScale(scale, BigDecimal.ROUND_HALF_UP); 
    return bd; 
} 
 
public static void main(String[] args) { 
    System.out.println(roundNumber(9.994, 2).doubleValue()); 
    System.out.println(roundNumber(9.995, 2).doubleValue()); 
    System.out.println(roundNumber(9.996, 2).doubleValue()); 
} 

とすると9.995の四捨五入の結果が10.0ではなく9.99になってしまいます。


防御的プログラミング

防御的コピー

仮にコンストラクタでjava.util.Date型の値を受け取りfinalなメンバとして保持しても、外部から値を変更できてしまいます。
内部的にはDate#getTimeの値を保持しておき必要に応じてDate型に変更するほうが安全です。
このように可変な要素を持つものを保持する場合は防御的にコピーする必要があります。
ただしコピーすることが大きなコストであったり、利用者が同一パッケージで信頼がおける場合はこの限りではありません。
そのような場合は、できればコメントに理由があって防御的コピーを行っていない旨を記載しておくべきです。


java.util.Dateで外部とやり取りしているクラスの防御有無の各パターンのサンプルコード

●防御的コピーをしていないパターン(コンストラクタもgetterも内部表現を暴露しており、外側からDateHolderの内部表現の日時を変更できてしまう)

public final class DateHolder {
        
    /** 日時 */
    private final Date date;
        
    public DateHolder(Date date) {
        if (date == null) {
            throw new NullPointerException("parameter date is null.");
        }
        this.date = date;
    }
        
    public Date getDate() {
        return date;
    }
}


●防御的コピーをしているパターン(外側からDateHolderの内部表現の日時を変更できない)

public final class DateHolder {
    
    /** 日時(ミリ秒表現) */
    private final long dateMillisec;
    
    public DateHolder(Date date) {
        if (date == null) {
            throw new NullPointerException("parameter date is null.");
        }
        this.dateMillisec = date.getTime();
    }
    
    public Date getDate() {
        return new Date(dateMillisec);
    }
}


■String配列で外部とやり取りしているクラスの防御有無の各パターンのサンプルコード

●防御的コピーをしていないパターン(コンストラクタもgetterも内部表現を暴露しており、外側からStringArrayHolderの内部表現の文字列配列の各要素の値を変更できてしまう)

public final class StringArrayHolder {
    
    /** 文字列の配列 */
    private final String[] strArray;
    
    public StringArrayHolder(String[] strArray) {
        if (strArray == null) {
            throw new NullPointerException("parameter strArray is null.");
        }
        this.strArray = strArray;
    }
    
    public String[] getStrArray() {
        return strArray;
    }
}


●防御的コピーをしているパターン(外側からStringArrayHolderの内部表現の文字列配列の各要素を変更できない)

public final class StringArrayHolder {
    
    /** 文字列の配列 */
    private final String[] strArray;
    
    public StringArrayHolder(String[] strArray) {
        if (strArray == null) {
            throw new NullPointerException("parameter strArray is null.");
        }
        this.strArray = strArray.clone();
    }
    
    public String[] getStrArray() {
        return strArray.clone();
    }
}


名前付け

型を誤解させるような変数名をつけない

以下のような変数宣言があった場合に、

private Map<String, String> addressArray;

この変数を使用しているメソッド等で、"addressArray"という変数名だけ見た場合に対象変数の型は、何かの配列型、ArrayList、独自型あたりだと推測してしまいます。
普通はMap型だとは思わないでしょう。
このように型を誤解させるような変数名はつけないようにした方が良いです。型を誤解させるとコードの可読性が下がります。


配列

配列に意味を持たせない

以下のような(各要素ごとに)意味を持たせた配列を使用すべきではありません。対象の配列を使用しているコードの意味が分かりにくくなりバグ混入の可能性が高まります。
配列の代わりに独自のオブジェクト(クラス)を定義すべきです。

// 氏名と郵便番号、住所を格納する配列
String[] personData = new String[3];
personData[0] = "Taro Yamamoto";    // 氏名
personData[1] = "999-1234";         // 郵便番号
personData[2] = "埼玉県";           // 住所
return personData;

上記の配列の場合、要素数や要素の順番が変わってしまうとそれだけでデータとして不整合な状態となってしまいます。
外部とのインターフェースの都合上どうしても意味を持たせた配列が必要な場面がありますが、そのような箇所は局所的にすべきです。
自システム内に意味を持たせた配列を持ち込みすぎるとコードの可読性が圧倒的に下がります。


コメント

外部に公開するAPIにはJavadocを記述する

外部(別システム)に公開する等で作成者以外が使用することが考えられるAPIについては基本的にjavadocを記載した方が良いです。
対象のクラスがスレッドセーフか、スレッドセーフではないのかを記述してあると利用者側にとってはさらに良いです。
(可能ならば全ての変数、メソッドにjavadocコメントが記述してあるとより良いです)

コードをコメントアウトして残さない

ソースコード内に時々コメントアウトされた処理が存在しますが。コメントアウトを行った人間以外から見て、そのコメントアウト部分のコードはノイズにしかなりません。
基本的に不要となった処理はコード上に残さず、即時に削除すべきです。(過去に戻りたい場合はバージョン管理システムの出番です!)
どうしても何らかの理由があって処理コードをコメントアウトして残す場合は、どういう理由でコメントアウトしてあえて残しているのかを強くコメントに記述(補足)しておくべきです。
ただし、残しておくべき理由がなくなったらコメントアウトしたコードは削除すべきです。
(本件はJavaに限らずバージョン管理されている全プログラムにおいて言えることです)


JUnit

テストし易いコードを書く

  • ユニットテストの対象クラスやメソッドが、テストしにくければ、当然ですがJUnitでテストケースを書くのは困難になります。
    テストし易いコードを書くように心掛けた方が良いです。
    テストしにくいということは、実際に(対象のメソッドやクラスを)利用する側から見ても扱いにくいメソッド(やクラス)であると言えます
  • テストし易いメソッドとは?
    • 引数を取る
    • 引数の数が少ない
    • メソッド内の処理中に引数を変更しない
    • 戻り値がある
    • 同一の引数に対して常に同一の戻り値を返す
    • そのメソッドを提供するクラスのインスタンス化がし易い
    • 外部環境(外部システム)に依存していない

Mockライブラリ

ユニットテストを書く際に、Mock化したインスタンスを生成するライブラリを使用すると、ユニットテストケース作成の難易度が下がる場合もあります。
代表的なMockライブラリは以下になります。

  • Mockito
  • JMockit


適切なAPIを使用する

実行効率の悪いコンストラクタ

以下に示すコンストラクタは非効率であるため使用すべきではありません
代替案を使用すべきです。

  • new String()
    • 代替案: ""(空文字列)を使用すべきです
  • new String(String)
    • 代替案: 引数にとっている文字列をそのまま使用すべきです
  • new Integer(int)
    • 代替案: Integer.valueOf(int) を使用すべきです(その他のプリミティブ型でも同様)
  • new Boolean(...)
    • 代替案: Boolean.valueOf(...) を使用すべきです


初期化

staticイニシャライザに複雑な処理を書かない

staticイニシャライザ(静的初期化子)でネストが深くステップ数の多い処理を行うのはNGです。
staticイニシャライザはメソッド等とは違い引数も戻り値もないため、複雑なロジックを書いてもユニットテストでの確認が困難となります。
テスト容易性を確保する上でもstaticイニシャライザに複雑な処理を記述すべきではありません。

インスタンスイニシャライザを使用しない

インスタンスイニシャライザは存在があまり知られていないインスタンスの初期化方法のため基本的に使用すべきではありません。
インスタンスの初期化処理は、コンストラクタに記述すべきです。インスタンスイニシャライザを使用するとコードの可読性が落ちてしまいます。
(ただし、無名クラスにおける初期化処理においては有効に利用できます。が、使用するとしてもその用途に限るべきです。)


クラス構成

ユーティリティクラス

Javaでユーティリティクラスの様なstaticメソッドのみを持つクラスのソースコードの典型例は以下の通りです。

public final class SampleUtils {

    public static void func1() {
        // do something
    }
    
    public static void func2() {
        // do something
    }

    /**
     * インスタンス生成の抑制
     */
    private SampleUtils() {
    }
}

上記コードのポイントは以下2つです。

  • クラスをfinalにして継承による拡張を防ぐ(staticなクラスなので継承は想定されない)
  • コンストラクタをprivateにしてインスタンス化を抑制する(インスタンス化する意味が無いため)

実装クラスは基本的にfinalに

abstractでない(継承されることを想定しない)実装クラスには基本的にfinal修飾子を付与して下さい。
特に他システム等の外部と連携するために公開している実装クラスをfinalにしていない場合、思いもよらない継承をされてしまう場合があります。
想定外のサブクラスが存在してしまうと継承元のクラスが簡単に修正できなくなってしまいます。

パッケージプライベートによるクラスの隠蔽

以下のように実装クラスの可視性をパッケージプライベートにすることで、外部に対して実装を隠蔽することができます。
(※本内容はどんな場面でも使用できるわけではないですがJavaの道具立ての一つとして理解しておくことで、クラス構成・パッケージ構成を考える際の役に立つと思います)

外部に公開しているプロジェクトのパッケージ及びクラス構成は以下の通りとする。
(org.sample.somepackage.apiパッケージに3つファイルがあるとする)

org.sample
└─somepackage
    └─api
         SomethingInterface.java
         SomethingImpl.java
         SomethingFactory.java

各ファイルの概要及び可視性は以下の通り

ファイル 可視性 概要
SomethingInterface.java public 外部が使用することを想定したインターフェース
SomethingImpl.java package private SomethingInterfaceの実装クラス
SomethingFactory.java public SomethingInterfaceの実装を生成して返却するファクトリー

この構成の主な利点は以下2つです。

  • ①外部(他システム、他パッケージ)からインターフェースに対する実装が隠蔽される
    • 外部からSomethingInterfaceを使用したい場合、SomethingFactoryを経由してしか(実装が)取得できない
    • 外部からはSomethingImplの存在が全く見えない
  • ②実装クラスのユニットテストはできる
    • SomethingImplは外部からは操作できないが同一パッケージからは見えるので、同一パッケージ上に配置するテストケースクラスからは直接操作(new)してテストすることができる。

Immutableなクラスにすることを考える

Immutableなクラスはマルチスレッド環境でも同期処理等無しで正常に動作します。クラスをImmutableで設計することは重要です。
Immutableなクラスの例は以下の通りです。
メンバがfinalであるため、コンストラクタ実行後に変更することができません。一般的なJavaBeansと違ってsetterを持ちません。

public final class ImmutableObject {

    private final int id;

    private final String value;

    public ImmutableObject(int id, Sring value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }
    
    public String getValue() {
        return value;
    }
}

メソッド間の値の受け渡し用インスタンス変数を作らない

以下のサンプルコードではexecuteMainLogicメソッドからexecuteLogicDetailをメソッドを呼び出す際の値の受け渡しにインスタンス変数tempVariableを利用しています。
このように、インスタンス変数をメソッドの値の受け渡しのために使用するのはNGです。これを行うと対象クラスはスレッドセーフではなくなります。
下記の例で言えばメソッド間の値の受け渡しには素直に引数を使用すべきです。

public class TempVariableTrial {

    /** executeMainLogic -> executeLogicDetail へ値を渡すための変数 */
    private int tempVariable = 0;
    
    public void executeMainLogic(int value) {
        
        if (value < 0) {
            tempVariable = -1;
        } else {
            tempVariable = 1;
        }
        
        executeLogicDetail();
    }

    private void executeLogicDetail() {
        if (tempVariable > 0) {
            // do something tempVariable positive number
        } else {
            // do something tempVariable zero or negative number
        }
    }
}

Nullオブジェクト

処理中にnullチェック、nullによる分岐が頻発している場合はNullオブジェクトの導入を検討した方が良いです。
処理中にnullを元にした処理が多いとバグが混入しやすくなったり、コードの可読性がおちます

外部に公開するAPI

Javaで外部に公開して利用してもらう手続き(API)の設計方針は以下の通りです。
(実際には外部に公開しないものであっても、以下を意識しておくことは重要です)

  • 手続きの名称が適切で、それを利用したコードの可読性が高いこと。
  • 利用者への要求が可能な限り少ないこと。(引数が少ない、API利用までのセットアップ等が少ない(無い))
  • 基本的には実装でやりとりせずインターフェースで会話させる。(引数、戻り値も可能ならインターフェースにする)
  • 引数、戻り値で受け渡すオブジェクトは極力Immutableにする。(不用意な破壊を防ぐ、スレッドセーフ性の確保)
  • スレッドセーフ性を確保する。(利用側にスレッドセーフにするための同期処理をさせなくて済むようにする)
  • 副作用を起こさない。(手続きの名前から想像できない外部環境へ影響のある処理をしない)
  • 公開する手続きがチェック例外をスローすると利用側にとって負担になることを理解しておく。

staticメソッドだけを持つクラスの注意点

スレッドセーフ性を意識したクラス設計をしていると全くインスタンス変数を持たない(インスタンスメソッドだけを持つ)クラスになる場合があります。
そういった場合に、都度インスタンスを利用側が生成するのも面倒だろうから、メソッドを思いっ切ってstaticメソッドに置き換えてしまっても良いのではないかという考えに陥る場合があります。
しかし、両者には決定的な違いがあることに注意が必要です。
staticメソッドだけにしてしまうとポリモーフィズムを全く利用できなくなるという点です。そうなると必ず実装を利用側に見せる必要がでてきます。
汎用的なユーティリティクラスをstaticメソッドのみのクラスとして実装するのはOKですが、外部に公開するような業務的なAPIをstaticにするのは避けた方が得策です。

クラスはできるだけ小さく

クラスのステップ数が肥大化するとプログラマの手に負えなくなってしまいます。できるだけクラスのステップ数を小さくするように努めるべきです。
ステップ数が肥大化したクラスに対して機能追加などのために変更が必要な場合、変更の影響範囲を的確に検出するための簡単な方法が無いため、
「編集して、そして祈る」というプログラミング手法になってしまいます。
そうなると変更にとても長い時間を要するか、バグが増加するかのどちらかになります。


生成処理

コンストラクタの隠蔽を検討する

実装クラスのコンストラクタを直接呼ばれるとクラス間の結合度が高くなってしまいます。
また、コンストラクタはコード上 "new" としか書けないので、どういうオブジェクトを生成するのか という意味を持たせるのが難しくなります。
全ての場面においてコンストラクタを見せないようにする必要はありませんが、外部に見せる実装クラスにおいてはコンストラクタを隠蔽する検討をした方がよいでしょう。
例えば、コンストラクタの代わりにstaticファクトリを提供することで、どういうオブジェクトを生成しようとしているかの意味が分かりやすくなったり、
生成処理を隠蔽することで、毎回新しいインスタンスを返すのではなく(キャッシュのように)1つのインスタンスを使いまわして返すことがやり易くなります。


■コンストラクタを公開(public)にして、利用側に毎回コンストラクタを実行してもらうパターン

/**
 * 処理結果のメッセージを表すオブジェクト(コンストラクタ公開パターン)
 */
public final class ResultMsg {

    /** 成功を意味するメッセージか? */
    private final boolean isSuccess;
    
    /** メッセージ文字列 */
    private final String message;

    /**
     * 結果メッセージを生成します。
     * 
     * @param isSuccess 成功を意味するメッセージの場合true, それ以外の場合false
     * @param message メッセージ文字列
     */
    public ResultMsg(boolean isSuccess, String message) {
        this.isSuccess = isSuccess;
        this.message = message;
    }

    /**
     * 成功を意味するメッセージか?を取得します。
     * @return 成功を意味するメッセージか?
     */
    public boolean isSuccess() {
        return isSuccess;
    }

    /**
     * メッセージ文字列を取得します。
     * @return メッセージ文字列
     */
    public String getMsg() {
        return message;
    }
    
    
    /**
     * 対象クラスを実行してみるためのサンプルmain
     * 
     * @param args 実行時の引数
     */
    public static void main(String[] args) {
        // コンストラクタを実行してオブジェクトを生成する
        ResultMsg okMsg = new ResultMsg(true, "The operation was successful.");     // 成功メッセージ
        ResultMsg ngMsg = new ResultMsg(false, "The operation was failed.");        // 失敗メッセージ
        ResultMsg emptyMsg = new ResultMsg(true, "");                               // 空メッセージ
    }
}

■コンストラクタを非公開(private)にして、利用側にstaticファクトリを実行してもらうパターン
(呼び出し側コードにおいて、成功 or 失敗のメッセージを生成していることが分かりやすくなる)

/**
 * 処理結果のメッセージを表すオブジェクト(コンストラクタ非公開パターン)
 */
public final class ResultMsg {
    
    /** 空を意味するメッセージ */
    private static final ResultMsg EMPTY_MSG = ok("");

    /** 成功を意味するメッセージか? */
    private final boolean isSuccess;
    
    /** メッセージ文字列 */
    private final String message;
    
    /**
     * 成功メッセージを生成します。
     * 
     * @param message メッセージ文字列
     * @return 成功メッセージを意味するオブジェクト
     */
    public static ResultMsg ok(String message) {
        return new ResultMsg(true, message);
    }
    
    /**
     * 失敗メッセージを生成します。
     * 
     * @param message メッセージ文字列
     * @return 失敗メッセージを意味するオブジェクト
     */
    public static ResultMsg ng(String message) {
        return new ResultMsg(false, message);
    }

    /**
     * 空を意味するメッセージを返します。
     * 
     * @return 空のメッセージを意味するオブジェクト
     */
    public static ResultMsg empty() {
        return EMPTY_MSG;
    }

    /**
     * 結果メッセージを生成します。
     * 
     * @param isSuccess 成功を意味するメッセージの場合true, それ以外の場合false
     * @param message メッセージ文字列
     */
    private ResultMsg(boolean isSuccess, String message) {        
        this.isSuccess = isSuccess;
        this.message = message;
    }

    /**
     * 成功を意味するメッセージか?を取得します。
     * @return 成功を意味するメッセージか?
     */
    public boolean isSuccess() {
        return isSuccess;
    }

    /**
     * メッセージ文字列を取得します。
     * @return メッセージ文字列
     */
    public String getMsg() {
        return message;
    }
    
    /**
     * 対象クラスを実行してみるためのサンプルmain
     * 
     * @param args 実行時の引数
     */
    public static void main(String[] args) {
        // staticファクトリー経由でオブジェクトを生成する
        ResultMsg okMsg = ResultMsg.ok("The operation was successful.");   // 成功メッセージ
        ResultMsg ngMsg = ResultMsg.ng("The operation was failed.");       // 失敗メッセージ
        ResultMsg emptyMsg = ResultMsg.empty();                            // 空メッセージ
    }
}


ロック

Semaphoreをバイナリセマフォとして利用する際の注意

java.util.concurrent.Semaphoreをバイナリセマフォ(いわゆるmutex)のように使用する場合は注意が必要です。
コンストラクタで指定したpermitsの初期値を1とした場合でも、releaseメソッドを呼び出す度に値が初期値を無視して2, 3, 4と増えます。
そのため、思いもよらないタイミングでtryAcquireが間違って成功してしまう場合があります。
(バイナリセマフォとして利用するには内部のpermitsが必ず0か1でのみ遷移するように外部から制御するなどの処置が必要になります)


リフレクション

リフレクションの利用は最低限にする

普通に書いたプログラムでは実現できないことが、リフレクションを使うと実現できたりします。
ただし、リフレクションを使いすぎると処理が分かりにくくなったり、速度的な問題などが出る可能性もあります。
普通のアプリケーションにおいては、リフレクションはあまり利用しないのが得策です。
使う場合も限定的であった方が良いです(フレームワークや特定のライブラリを実装する側etc)


セキュリティ

ライブラリの脆弱性

利用しているライブラリに時間経過とともにセキュリティ的な問題が見つかる場合があります。
定期的にOWASP Dependency Check等で脆弱性が存在していないかチェックした方が良いです


補足

長期的にシステムを育てていくためには、ここに書いた点以外でもプログラムの可読性やメンテナンス容易性の点での考慮も必要です。その点について以下エントリに書いています。


また、Javaでプログラムを書くという点とは少し異なりますが、システムを俯瞰してみた場合に考慮しておいた方がよい内容を以下に書いています

Go言語LT大会! 「最近、Go言語始めました」の会に行ってきた #golangjp

Go言語LT大会! 「最近、Go言語始めました」の会に参加してきました。

以下自分用のメモです。


概要

Go言語入門者のLT大会&懇親会

  • 開催日: 2017/6/5
  • 場所: レバレジーズ株式会社


おススメ・標準・準標準パッケージ20選

上田拓也 氏 (メルカリ/ソウゾウ)

  • Go標準のパッケージ
    • 様々な機能が提供されている
      • HTTPサーバ/クライアント
      • 文字列処理
      • 暗号化
      • 画像処理
    • 160パッケージぐらいある
  • Go標準のパッケージ
    • golang.org/xで提供されるパッケージ
    • 450パッケージ以上ある
  • fmtパッケージ
    • 書式付きプリント機能を提供する
    • %T, %q %vあたりが便利
    • %[1]d, %[2]dとかでN番目の値が出せる。引数の順番を指定できる。引数を使いまわすこともできる
    • fmt.Stringerが便利
      • JavaのtoString的な役割のインターフェース
  • ioパッケージ
    • IO関係の基本的な型が定義されている
      • io.Readerとio.Writerが強力
        • インターフェースの良い例
      • io.TeeReaderとかもあり便利
        • UNIXのteeコマンド的なもの
  • io/ioutilパッケージ
    • IO関係の便利関数がある
    • NopCloserが便利
    • ReadAllhあんまり使わない
      • ストリームのままやろう
  • stringsパッケージ
    • 文字列に関する処理を提供
    • strings.Readerが便利
      • 文字列をio.Readerに変換してくれる
    • strings.Fieldsが便利
      • ホワイトスペースでいい感じに区切ってくれる
  • bytesパッケージ
    • strringsにあるものはだいたいある
    • bytes.Readerが便利
      • []byteをio.Readerに変換できる
    • bytes.Bufferが便利
      • ゼロ値で扱える
  • bufioパッケージ
    • bufio.Scannerが便利
    • bufio.SplitFuncでカスタマイズ
  • encoding/binaryパッケージ
    • 構造体とバイト列をマッピングできる
    • Go1.9でmath/bitsが入る
  • x/textパッケージ
    • 日本語の文字コードに対応(EUCJO,Shift-JIS,ISO2022JPに対応)
    • 多言語化の機能を提供
    • transform.TransFormerが便利
      • ストリームのまま変換できる
      • 実際の例を何回か読まないと理解が難しい
  • flagパッケージ
    • コマンドライン引数に関する機能を提供
      • オプションパースが便利
      • flag.Argsでオプション以外の引数が取れる
      • ポインタの使用例としてGood
  • path/filepathパッケージ
    • ファイルパスに関係する処理を提供する
      • \や/を意識しなくてよい
    • filepath.Walkが便利
      • 再帰的にディレクトリを読んでいく
  • text/templateパッケージ
    • html/templateはHTML特化版
    • コマンドライン引数に使うと便利
    • コードジェネレーションにも利用できる
  • testingパッケージ
    • go testでユニットテストが実行できる
    • Go1.7からサブテストが可能
    • テストを並列で実行可能
      • t.Paralellを使う
    • カバレッジが取れる
      • -cover
  • net/httpパッケージ
    • HTTPサーバ.\/クライアントを提供する
      • http.Handlerを実装すればよい
      • インターフェース実装の良い例
      • 重量なWebフレームワークは必要ない
      • テストしやすい
  • net/http/httptestパッケージ
    • HTTPサーバのテストで使える機能を提供
      • ハンドラのテストが便利
      • httpdocも便利
  • syncパッケージ
    • ロックなどの機能を提供
      • sync.WaitGroupが便利
      • sync.Onceが便利
        • 1回だけ実行したい場合に楽
  • contextパッケージ
    • 複数のGoルーチンをまたいだキャンセル機能などに使える
    • context.Contextが強力
      • インターフェースなので拡張しやすい
    • errgroupパッケージも便利
  • imageパッケージ
    • 画像処理を提供するパッケージ
      • image.Imageがインターフェース
        • 実装すればなんでも画像として扱える
      • pngやjpeg,gifなどに対応している
  • x/imageパッケージ
    • 標準パッケージでは足りていない機能を提供
    • x/image/drawが描画機能を提供してくれる
    • フォントに関する機能も提供している
  • reflectパッケージ
    • リフレクションの機能を提供する
      • structタグにアクセスする唯一の方法
      • 使いどころを間違えなければ便利。遅いので乱用はNG
      • 食わず嫌いは良くない
      • パニックが起こりなくるのでちゃんとチェックを行うこと
  • goパッケージ
    • 静的解析の機能を提供する
      • 抽象構文木(AST)が取得できる
      • 式単位でもパースできる
      • 型チェックもできる
      • 自作開発ツールも作ることができる


Go開発合宿2017

@n0bisuke 氏

  • Goを書く合宿
    • いろんな人が集まってわいわいやるコミュニティ
  • 合宿場所にした土善旅館は開発合宿プランを用意している
    • 過去の合宿者の要望をもとにした優れた設備等がある


初心者がGoでCLIツールを作ってみて学んだこと

@syossan27 氏

  • 作ったOSS
    • kirimori
    • torisetsu
  • 初心者ならではの様々な失敗
    • packageで分ける粒度が分からず1つのファイルにすべて突っ込む
    • エラーハンドリング時に適当にpanicを使ったために自分がpani
    • 構造体メソッドンに対してポインタレシーバを使わない
    • やたら変数名を長くしてしまう


作って学ぶミニマムgolint

@minamijoyo 氏

  • golint
    • Goっぽくないコーディングスタイルを指摘してくれる
    • どうやって検出しているのか?
      • ただの辞書だった
  • よくわからないのでgolint作ってみた
    • 標準のgoパッケージを使う


j2hの紹介

@kanga333 氏

  • j2hを作った
    • JSONからHiveのDDLを出力してくれる
  • gjson
    • goでJSONを取り扱うライブラリの一つ
    • result.TypeでJSONのタイプを取得できる
    • ForReachで子要素に対して反復処理ができる


GoとLintのおいしい関係

@wata727 氏

GoとLintのおいしい関係 Go言語LT大会

  • Lint
    • コンパイラより詳細かつ厳密なチェック
  • Golint
    • Goのスタイルチェッカー
    • (個人的に)Golintの良いところ:行の長さを制限しない
  • Govet
    • コンパイラよりも詳しい検査を行う
  • Go Meta Linter
    • セキュリティとかtypoのチェックとか
  • goは標準パーサがある
    • 新しいシンタックスにも追従してくれる
  • Linterを作りやすい
  • GoではLintを使おう。Linterを作るならGoでやろう


Golangちゃんと始めてから1カ月経ちました

@nntsugu 氏

  • Datadog
    • デザインファーストなFWのgoaで書く
  • プチ合宿やりませんか?


We "Go" fast

@taison124 氏

  • Goで開発
    • 環境や作法が決まっていて迷いどころない
      • 暗黙知が排除されている
    • 標準のやり方でできる
  • 本質的な問題解決に時間や情報を集中できる

個人的にこのLTが一番ためになりました!!


コネクションプールをGoでどう作ろうか調べた話

Jumpei Mano 氏

  • golibmc
    • 1つのコネクションをロックしながら使いまわしている
    • ボトルネックになる
  • memcached用コネクションプールを実装するためにdatabase/sqlの実装を読んだ
    • 非常にいい勉強になった
    • Go特有のchannelを使うことで実装がすっきりしている


ずっとRubyをやっていたエンジニアがGoに入門して挫折して再挑戦した話

@suzan2go 氏

https://speakerdeck.com/suzan2go/zututorubywoyatuteitaenziniagagoniru-men-sitecuo-zhe-sitezai-tiao-zhan-sitahua

  • 敗因
    • Go言語でRubyのようなオブジェクト指向をやろうとしてしまった
    • Rubyのように楽に書く方法がないか探し求めてしまった
  • 再挑戦
    • GitHubでGo言語のアプリケーションを色々見てみる
    • Go言語の設計についての記事を色々読んでみる
    • Goに従ってみると楽しくなってきた
  • GoDocが凄い
  • Goに入ればGoに従え



関連エントリ

golang.tokyo #6 に行ってきたよ #golangtokyo

golang.tokyo #6 に参加してきました。

以下自分用のメモです。


概要

  • 開催日: 2017/6/1
  • 場所: 株式会社ディー・エヌ・エー


Gopher Fest 2017 参加レポート

上田拓也 氏 (メルカリ/ソウゾウ)

  • Gopher Festとは?
    • Google I/O に合わせて開催されるイベント
    • 5/15にサンフランシスコで開催
  • The State of Go
    • Goの開発状況
      • Go1.8は2/16にリリース済
      • Go1.9は5月1日にコードフリーズ済
    • 言語仕様の変更
      • 型のエイリアスを定義できるようになる
        • 完全に同じ型
        • キャスト不要
        • エイリアスの方でメソッドは定義できない
    • 標準ライブラリの変更
      • math/bits
        • ビット演算に便利なライブラリ
      • sync.Map
        • スレッドセーフなマップ
      • html/templateがより安全に
      • os.Exec
        • 重複する環境変数を削除するように、一番後ろのものを優先するようになった(直感的になった)
    • ツール類の変更
      • コンパイラのエラーメッセージの改善(少し優しくなった笑)
    • コンパイラの速度改善
    • go testの改善
      • 複数パッケージの場合にvendorを無視
    • go doc
      • フィールドにリンクが貼れるようになった


初めてGolangで大規模Microservicesを作り得た教訓

Yuichi MURATA (DeNA)

  • Golangで大規模なMicroservices構成のAndAppを作ったときに得た教訓
  • AndAppの基本構成/アーキテクチャ
    • Google App Engine / Go 1.6
    • Gin / Echo
  • 教訓1:フレームワークにこだわらない
    • Ginで始めた開発
      • シンプルで使いやすかった
      • App Engineとのインテグレーションもサンプルにあった
      • Framework Context vs App Engine Context 問題
        • gin,ContextからApp Engine Contextを派生させ回避  * Echo
      • ワイルドカードパスを共有したルーティングが可能
      • Webアプリ系のコンポーネント開発が捗る
      • 設計がガリガリ書き換わる
      • Version3でコンテキストの派生が不可能に
    • そもそもGoでフレームワークにこだわらない
      • 洗練されたnet/httpパッケージがある
      • Golangは言語仕様がシンプルかつgofmtがある
  • 教訓2: interfaceを尊重する
    • 独自のエラー型を定義して利用することに
    • Nilには型があるの罠
      • errorインターフェースと独自エラー型を混在した時に発生
    • 素直に他の関数にならってerrorインt-フェースに統一すればよかった
    • interfaceの定義されたものを積極的に使った方が標準ライブラリとの連携がシンプルになる
  • 教訓3: regex compile / reflection は遅い
    • 複数スキーマを読み込むためにgojsonchemaを選択
    • パフォーマンステストで問題が発覚
      • バリデーションの有無でパフォーマンスに顕著な差が出ることがわかった
      • パースの度にRegexコンパイルしていた
      • 動的にスキーマを解析する部分でrefletionを多用していた
    • そもそもgo-jsvalで頑張ればさらに段違いに良い結果になった・・
    • regex / reflection などのコスト高の操作を意識する
  • 教訓4: 非対称暗号は遅い
    • Microservicesにおいて肝となる認証・認可
      • JSON Web Token / Json Web Signatureを活用している
    • 非対称鍵の署名が重い
      • opensslと比べgoのcrypto/rsaが貧弱
      • 対象会議の署名:0.37msec, 非対称鍵の署名:486msec
    • cgoを使ったopensslのバインディングも存在する
  • まとめ
    • 困ったときにGoの哲学に帰りシンプルなアプローチをとる
    • Goを過信せずにパフォーマンスに気を配る


ゲーム開発には欠かせない?!あれをシュッと見る

Ryosuke Yabuki (カヤック)

  • CSV
    • CSVは視認性が悪い
    • カラムとデータの関係性が見づらい
  • csviewer
    • CSVをいい感じに表示するコマンドラインツール
    • CSVをテーブル形式で表示してくれる
    • 絞り込みなどもできる
  • 実装で使った便利だったライブラリ
    • sliceflag
      • 複数の値を肝がんに受け取ることができる
      • flagの普段の使い方と大きく変わらない
    • tablewriter]
      • CSVを表示するだけなら相当少ないコード量で済む


Go Code Review Comment を翻訳した話

鎌田健史 氏 (KLab)

  • Go Code Review Commentsとは
    • コードレビューする際にます見るべき箇所をまとめたもの
    • Effective Goを簡単にしたもの
  • 幾つかのカテゴリに分かれている
    • コードの見た目を改善
      • golintめっちゃ優秀
    • コメントや文章の体裁
    • tips系
    • 設計の指針になるようなもの
      • レシーバをポインタにするかどうか迷ったら読もう


ScalaからGo

James (エウレカ)

  • Scala
    • 経験者は関数型っぽく書く
  • 関数型開発はGoでできますか?

  • No!
  • 関数型開発のコンセプトはGoで使える?
    • Yes!
  • 関数型開発とは?
    • 副作用がない開発
  • コードレベル
    • 部分適用はGoでもできる
  • ScalaとGoどっちが好き?
    • Scalaが好き
    • 企業としてはGoがいいかも。Goの方が初心者でも綺麗なコードが書ける


Crypto in Go

Kengo Suzuki (マネーフォワード)

  • Goにおける暗号アルゴリズムを利用する
    • ECBモード
    • パディングとHMACの実装面倒くさい
    • ASE + GCM
      • とてもシンプル



関連エントリ・リンク

Go言語 - 日時のフォーマット処理

本エントリでは、Go言語で日時を特定の書式にフォーマットする処理を実行してみます。

基本的にはtimeパッケージのTime.Format関数を利用することになります。


例えばJavaなら、LocalDateTime#format やら SimpleDateFormat#format 等を利用してフォーマットを行いますが、
その時に与える書式は例えばyyyy/MM/dd HH:mm:ssという文字列になります。

で、これと同じことをGoでやろうとした場合、指定する書式は

2006/01/02 15:04:05

です。具体的な上記の日時を書式として与える必要があります。

慣れの問題なんでしょうけど、具体的な値を指定するというのは私には違和感がありました。

一応典型的な書式はtimeパッケージでconstとして提供されています。(が、日本人好みのスラッシュが入った書式は無いです・・)

const (
        ANSIC       = "Mon Jan _2 15:04:05 2006"
        UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
        RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
        RFC822      = "02 Jan 06 15:04 MST"
        RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
        RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
        RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
        RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
        RFC3339     = "2006-01-02T15:04:05Z07:00"
        RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
        Kitchen     = "3:04PM"
        // Handy time stamps.
        Stamp      = "Jan _2 15:04:05"
        StampMilli = "Jan _2 15:04:05.000"
        StampMicro = "Jan _2 15:04:05.000000"
        StampNano  = "Jan _2 15:04:05.000000000"
)

ちなみに、与えられる日時は上記の値(2006年1月2日 15:04:05)に固定されていて、例えば年部分を 2017 にしたり、月部分を 12 にしたり とかはできません。(やると結果がおかしなことになります)

ただし、12時間表記にするために時間部分の 15 を 03 にしたり、 ゼロ埋めしないように 秒部分の 05 を 5 にしたり等はOKです。


以下で現在日時を色々な書式でフォーマットして出力してみました。

■サンプルコード

package main

import (
    "fmt"
    "time"
)

func main() {
    nowTime := time.Now()

    const format1 = "2006/01/02 15:04:05" // 24h表現、0埋めあり
    fmt.Printf("now -> %s\n", nowTime.Format(format1))

    const format2 = "2006/1/2 3:4:5" // 12h表現、0埋め無し
    fmt.Printf("now -> %s\n", nowTime.Format(format2))

    const DateFormat = "2006/01/02"
    const TimeFormat = "15:04:05"
    const MilliFormat = "2006/01/02 15:04:05.000"
    const MicroFormat = "2006/01/02 15:04:05.000000"
    const NanoFormat = "2006/01/02 15:04:05.000000000"

    fmt.Printf("yyyy/MM/dd -> %s\n", nowTime.Format(DateFormat))
    fmt.Printf("HH:mm:ss   -> %s\n", nowTime.Format(TimeFormat))

    // ミリ秒まで出力
    fmt.Printf("Milli -> %s\n", nowTime.Format(MilliFormat))

    // マイクロ秒まで出力
    fmt.Printf("Micro -> %s\n", nowTime.Format(MicroFormat))

    // ナノ秒まで出力
    fmt.Printf("Nano  -> %s\n", nowTime.Format(NanoFormat))

    // Unixtimeに変換
    unixTime := nowTime.Unix()
    fmt.Printf("unixTime -> %d\n", unixTime)
}


■実行結果

now -> 2017/05/24 19:21:04
now -> 2017/5/24 7:21:4
yyyy/MM/dd -> 2017/05/24
HH:mm:ss   -> 19:21:04
Milli -> 2017/05/24 19:21:04.135
Micro -> 2017/05/24 19:21:04.135562
Nano  -> 2017/05/24 19:21:04.135562200
unixTime -> 1495621264


というわけで無事に日時のフォーマット処理ができました。(2006/01/02 15:04:05を頭に入れとこう。)



関連記事

Go言語 - melodyを利用してWebSocketサーバを立てる

melodyGinを使ってWebSocketサーバを立てて、簡易的なチャット画面を作ってみました。

実際には、melody Exampleを、ほぼそのまま流用しているだけです。


準備

以下を実行してmelodyを利用可能な状態にしておきます

go get gopkg.in/olahol/melody.v1


プログラムとhtml

WebSocketサーバと動作確認用チャット画面のindex.htmlとして以下を用意しました。

■WebSocketサーバ(Goプログラム)

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "gopkg.in/olahol/melody.v1"
)

func main() {
    log.Println("Websocket App start.")

    router := gin.Default()
    m := melody.New()

    rg := router.Group("/sampleapp")
    rg.GET("/", func(ctx *gin.Context) {
        http.ServeFile(ctx.Writer, ctx.Request, "index.html")
    })

    rg.GET("/ws", func(ctx *gin.Context) {
        m.HandleRequest(ctx.Writer, ctx.Request)
    })

    m.HandleMessage(func(s *melody.Session, msg []byte) {
        m.Broadcast(msg)
    })

    m.HandleConnect(func(s *melody.Session) {
        log.Printf("websocket connection open. [session: %#v]\n", s)
    })

    m.HandleDisconnect(func(s *melody.Session) {
        log.Printf("websocket connection close. [session: %#v]\n", s)
    })

    // Listen and server on 0.0.0.0:8989
    router.Run(":8989")

    fmt.Println("Websocket App End.")
}


■index.html

<html>
  <head>
    <title>Chat powered by Melody</title>
  </head>

  <style>
    #chat {
      text-align: left;
      color:#ffffff;
      background: #113131;
      width: 400px;
      min-height: 300px;
      padding: 10px;
      font-family: 'Lucida Grande', 'Hiragino Kaku Gothic ProN', 'ヒラギノ角ゴ ProN W3', 'Meiryo', 'メイリオ', sans-serif;
      font-size: small;
    }
  </style>

  <body>

    <center>
      <h3>Sample Chat</h3>
      <pre id="chat"></pre>
      <label id="title"></label>
      <input placeholder="say something" id="text" type="text">
    </center>

    <script>
      var url = "ws://" + window.location.host + "/sampleapp/ws";
      var ws = new WebSocket(url);
      var name = "Guest-" + Math.floor(Math.random() * 1000);
      var chat = document.getElementById("chat");
      document.getElementById("title").innerText = name + ": ";
      
      var text = document.getElementById("text");
      var now = function () {
        return new Date().toLocaleString();
      };

      ws.onmessage = function (msg) {
        var line =  now() + " : " + msg.data + "\n";
        chat.innerText += line;
      };

      text.onkeydown = function (e) {
        if (e.keyCode === 13 && text.value !== "") {
          ws.send("[" + name + "] > " + text.value);
          text.value = "";
        }
      };
    </script>

  </body>
</html>


実行してみた

対象のアプリを実行します。実行すると以下のようなログがコンソールに出力されます

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /sampleapp/               --> main.main.func1 (3 handlers)
[GIN-debug] GET    /sampleapp/ws             --> main.main.func2 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8989


http://localhost:8989/sampleapp/にアクセスすることでチャット画面が利用できました


f:id:nini_y:20170523160815g:plain


というわけで、お遊び程度ですがかなりコード量少なくWebSocketのサーバサイドを実装することができました。



関連エントリ