プログラム

Posts filed under プログラム

javaでHTTPS通信の実装をするという記事を適当に書くのはやめるべき

Filed in ゴミコード, コンピュータ, プログラム

いろんなブログでjavaを使ったHTTPSクライアントの実装について書かれている。
だが適当な記事が多すぎて困る。RFCを読んでいるのか疑問に思うことがほとんどだ。というかRFCの存在を知らないのかもしれない。

このブログではリンクは基本的に張らないことにしているが特別に張っておこう。
こちらはIPAが提供してくれているHTTP オーバー TLSを規定したRFC2818の和訳である。
http://www.ipa.go.jp/security/rfc/RFC2818JA.html#31
こちらはRFC9110の日本語訳である。(RFC9110はRFC2818の修正版)
https://tex2e.github.io/rfc-translater/html/rfc9110.html

javaはセキュリティライブラリをJSSEとしてコアパッケージと分離することでセキュリティ機能をサードパーティによるカスタマイズが可能となるような思想で設計されている。そのためSSL/TLSについても柔軟なカスタマイズが可能である。

逆に言うとしっかりと理解していないとセキュリティホールを作りこんでしまうことになる。世のブログはこれに該当してしまう記事が異常に多い。さらに悪いのはそれが正解であるかのように広がってしまっていることだ。
エンジニアの中にもそういったブログの記事が正解だと思っている人が思いのほか多いという事実があっては笑ってもいられない。

よく紹介されているHTTPSクライアントの実装はこうである。

TrustManager[] tm = {
      new X509TrustManager() {
           public X509Certificate[] getAcceptedIssuers() {
               return null;
           }
           public void checkClientTrusted(X509Certificate[] xc,String type) {
           }
           public void checkServerTrusted(X509Certificate[] xc,String type) {
           }
     }
};
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, new SecureRandom());

URL url = new URL("https://hogehoge/foo/bar");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setDefaultHostnameVerifier(new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) {return true;}
    }
);

  以下略

まあ通信できるでしょう。しかしこれはやるべきことを全くやっていません。
このコードには簡単に以下の2つの問題がある。

  • 証明書が信頼できるかをチェックしないトラストマネージャを無条件で使っている(1~13行目)
  • サーバ名の検証を無条件でOKにしている(17~20行目)

1点目の問題では、証明書が送られてきさえすればそれが偽造されたものであろうと何だろうと通信OKとなってしまう。
2点目の問題では、信頼できる認証局から発行された証明書でありさえすればそれが目的のサーバのものでなくても通信OKとなってしまう。

1点目の偽造証明書にOKを出すのがダメなことは直感的にも分かるでしょう。
では、2点目はなぜダメなのでしょう。

たとえば、こんなケースがある。

上記のような実装をされたウェブブラウザであなたが銀行のサイトに接続したつもりでいます。
しかし、あなたが使っているDNSがレコード書き換えの攻撃を受けており実際には別のサイト(攻撃者が管理するサイト)に繋がってしまいました。
ですが、そのサーバには第三者機関から発行された証明書が設定されていました。
ブラウザは通信相手は期待したサーバであると判断して通信を続行してしまいます。
あなたは銀行のサイトだと思ってIDとパスワードを入力してしまいます。
攻撃者はあなたのIDとパスワードをまんまと手に入れてしまうわけです。

本来は偽のサーバに設定されている証明書のサーバ名とあなたが接続しようとしたサーバ名は異なっているので、接続は拒否されるべきである。ですが上記のコードではこれがなされません。さらに悪いことに上記のコードでは無条件で処理がされてしまうためユーザがそんなことが起こっていることを知る術もない。

では正しく処理するにはどう実装したらよいのだろうか。こうです。

URL url = new URL("https://hogehoge/foo/bar");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
以下略

非常に簡単です。なんのことはない。javaは特に設定を変えない限りデフォルトで正しく動作をするようになっている。
無理やりその動作を歪めてしまう必要はないのである。

自己署名証明書を設定したサーバに対してはこのままで通信をしようとしても通信はできない。
それは当然である。なぜなら自己署名証明書は信用できる証明書ではないからだ。
どうにもならないのかといえばそんなことはない。

自己署名証明書を信用できるという前提でクライアント側のトラストストアにインポートすればよいのだ。
これにより通信は可能になる。
ただ、これはあくまで普通の行為ではないということを認識したうえで行う必要がある。

セキュリティとはかくも面倒くさいことであるが、こういったことが守られてようやく実用レベルの安全性をもったネットワークというのが実現できるのである。守られていてもクラッカーとはイタチごっこであるのに守られていない場合に安全であるなど言えっこないのである。

見ろ!!コードがゴミのようだ!! part3

Filed in ゴミコード, プログラム

さて、、次のjavaのコードを見て貰いましょう。
mainでほぼ同じ処理を2回しています。
違いは呼び出しているメソッドがtest1,test2と違うだけ。
そしてtest1,test2の違いはメソッド内の最初の行だけです。
結果を受け取った後に戻り値を更新しています。

その後、fooXの値を2行ずつ出力していますが何が出力されるか分かりますか?

public class Test {
	public static void main(String[] args) {
		Test test = new Test();

		Bar bar1 = new Bar(100, "abc");
		Foo foo1 = new Foo(bar1);
		Bar ret1 = test.test1(foo1);
		ret1.i = 10000;
		ret1.s = "ABC";
		System.out.println(foo1.bar.i); //1回目処理の出力1
		System.out.println(foo1.bar.s); //1回目処理の出力2
		
		Bar bar2 = new Bar(100, "abc");
		Foo foo2 = new Foo(bar2);
		Bar ret2 = test.test2(foo2);
		ret2.i = 10000;
		ret2.s = "ABC";
		System.out.println(foo2.bar.i); //2回目処理の出力1
		System.out.println(foo2.bar.s); //2回目処理の出力2
	}

	public Bar test1(Foo foo) {
		Bar bar = new Bar(foo.bar.i, foo.bar.s);
		// ここでいろんな処理
		return bar;
	}

	public Bar test2(Foo foo) {
		Bar bar = foo.bar;
		// ここでいろんな処理
		return bar;
	}
}

class Foo {
	Bar bar;
	public Foo(Bar bar) {
		this.bar = bar;
	}
}

class Bar {
	int i;
	String s;
	public Bar(int i, String s) {
		this.i = i;
		this.s = s;
	}
}

答えはこうです。

100
abc
10000
ABC

コードのどこを見てもfooXの更新なんてないですね。でも値は変わります。
test1とtest2で違いが出る理由が分からなかった人はもう少し頑張りましょう。
そして分かった人もtest2のようなメソッドは書かないようにしましょう。

test2メソッドを書いた人はjavaを理解していないか、
保守を全く考えていないか、
小さいプログラムしか書いたことが無いかの三択でしょうか。。

エンハンスやバグ修正などでfooXの更新場所を探さないといけなくなったらどうするんでしょうか。。
test2のようなのがあると機械的に探すのがほぼ不可能です。
こんなじゃTDDだってできないだろうに。。

プログラマ35歳定年説に物申さない

Filed in プログラム

プログラマ35歳定年説というものがある。
35歳になると能力が衰えて技術習得が追い付かず時代の流れについていけなくなるというものである。

これについては常々違和感を感じていた。
名目上は頭脳労働であるプログラマが35歳程度で働けなくなるなんてあるのだろうかと。
しかし、自分自身がその年齢になってみると確かにいろいろ厳しいと感じることが多くなってきた。

私自身が感じていることをいくつか述べてみたいと思う。

  1. 糞な設計に強烈な違和感を感じるようになった。
  2. 割り込み作業が増えた。
  3. コードを書いてもなぜかあまり評価されない。

それぞれについて少し説明をしたいと思う。

1.糞な設計に強烈な違和感を感じるようになった。
いろいろな経験や勉強を経て、自分自身が最も良いと思っている設計思想が確立されたと思っている。
そのためかダメな設計に対して違和感を感じるようになってきた。

私自信が設計で気をつけることはいろいろあるものの、最も気を配っているのは適切なスコープである。適切なスコープを切るためには適切なクラス設計、コンポーネント設計、アーキテクト設計が必要という比較的上流のレベル、利用する付近で変数宣言をする、関連処理は一箇所に纏めるひいてはメソッドとして切りだせるという下流レベルまで一通り適用できる。

しかしこれに反するものを見たときになんとなく拒否反応が出るようになってきてしまった。こんなことではいけないと思いつつもどうもだめだ。

メソッドの先頭で10も20も変数が定義されているのに、メソッドの中身は実質いくつものブロックに分かれており、それぞれのブロックでは数個の変数しか使わないというコードは比較的よく見かける。
こんなものは変数定義を利用箇所の直前に変えメソッドを分割するべきである。20も変数を覚えつつメソッドの処理を追うなんて人間の脳の処理能力を超えているのである。

昔は頑張って読み解いていたが歳を取るごとにバカバカしさの方を強く感じるようになってきてしまっている。立場的にも指導する立場になり批判的にコードを見ているということが少なからず影響しているのかもしれない。

2.割り込み作業が増えた。
コード書きとは関係のない割り込み作業が非常に増えたと感じている。年齢というより立場の問題ではあるがそのような立場になる時期と年齢は無関係ではないだろう。なにがしの状況はどうなっているのか報告書を書けだの、進捗はどうなのか数字を纏めろ、突如発生した問題の対策会議を開くから集まれだといった作業が10年前に比べてやけに増えた。

正直、現場のプログラマへの配慮が足りないと思う。1時間を6回与えられた6時間と6時間を1回与えられた6時間はプログラマにとって意味が違う。どういうわけかそういうところに配慮してくれる人は少ない。

個人的な意見では作業が中断されるとだいたい30分から1時間くらいの作業内容は失われるくらいに考えて貰いたいのである。なるべく効率よく再開できるように手は打つが効率が悪くなることは避けられない。

3.コードを書いてもなぜかあまり評価されない。
これは開発手法やプロジェクトにも依存する話だと思う。ただ、私が関わったプロジェクトでは管理層がコードを見れない、見ないことが多かった。なので良いコードを書いているか悪いコードを書いているか判断しようがないというのはあったのかもしれない。正直これは良くないと思う。管理者もコードを書けとまでは言わないが見るくらいはすべきだと思う。そうでなくてもサブリーダーなどに誰が綺麗なコードを書いているかくらいは確認すべきだと思う。

比較的先進的な企業ではマネージャ層もある程度積極的にコードを書くそうだがさもありなんという感じである。なぜならコードの善し悪しはお金に直結する。綺麗なコードにはバグが入りにくい。少なくともバグが見つけやすい。修正もしやすい。拡張もしやすい。初見でも理解しやすく手が空いている人を必要な時だけ連れてきて保守させることができるため専属の保守員というのも不要になる。非常に多くのメリットがある。

だがこれらはあまり目に見えるものではない。いや、目には見えているはずである。だが何故そうなのかが考えられて評価に結び付くことが少ない。評価されるべきメンバー、あるいはふざけるなというコードを書いたメンバーはその時にはすでにプロジェクトから外れているということも多い。
そういったこともあり、バグ数の多寡は評価されてもコードの善し悪しが評価されることは少ない。そのせいなのか良いコードを書く人もそうでない人もひとくくりにされてしまってコードを書く行為への評価が高くなく、コードを書くのは給料の低い人となっている感がある。そのため給料が上がってくる=>コードを書かなくなってくる=>書けなくなるということがあるように思われる。

私の経験上、汚いコードを書く人が多いプロジェクトは属人的になっていることが多く、人員の入れ替えが難しい。新たな人を投入してもコードの理解が難しいため戦力になるまでに長い時間を要すが、管理者はそれが分かっていないから人を増やした分だけ進捗が上がると思っている。その分を古参のメンバーが頑張ってカバーしようとするが、時間が足りないため小手先のコードが増えてますますコードが汚くなっていくという悪循環となる。こうなるともうおしまいだ。

繰り返し言うがコードの善し悪しはお金に直結する。高給取りがコードを書いていても何もおかしいことは無いはずだ。彼・彼女の書いたコードは綺麗なのでお金がかからなくていいね~という会話がされるようなプロジェクトに参加したいものである。

さて、いろいろ書いてきたがそういうわけで年齢によりある種の壁にぶつかるということはあるのかもしれないと考えるようになってきた。

だがまあ仕事でコードを書くことは減ったとしても、技術的に付いていけるかどうかということについては最終的には個人の意識の問題だ。仕事はどうあれ生涯プログラマであると言えるように常に技術だけは磨いていきたいと思っている。

デジタルの脳を持つ人々

Filed in プログラム

1つ目のジョブが成功なら2つ目のジョブを実行するという処理を
何の疑問も持たずにこんなコードで書く人達がいる。

public enum Result {
	SUCCESS,
	FAILED
}

public class TaskExecuter {
  public void execute(Job job) {
     job.firstTask();

     if( job.getResult() != Result.FAILED ) {
        job.secondTask();
     }
  }
}

これはダメなコードです。さて、何がダメでしょうか。

エンハンスによりEnumがこうなったらどうでしょう。

public enum Result {
	SUCCESS,
	FAILED,
	SUSPEND
}

そう、もう期待通りには動きません。
前の処理が終わっていないのに次の処理を実行してしまいます。
なぜこんなコードを書いてしまうのでしょうか。

それは「失敗でなければ成功」という思い込みですね。
失敗でなければ成功などとは考えてはいけません。
「失敗でなければ失敗していない」以上の何物でもないのです。

私の経験上、思い込みというのはバグの発生源としてかなりの勢力を持っています。
また、バグが見つけられない理由としても一大勢力を築いています。

そんなわけでこう書くべきなのです。

if( job.getResult() == Result.SUCCESS ) {
   job.nextTask();
}

成功したとき以外には動いてもらっては困るのですから成功したら実行と素直に書けばよろしいのです。

同じようにこれはダメです。

if( job.getResult != Result.SUCCESS ) {
  throw new SomeException("errorだよ");
}

答えを曖昧にする日本人なのになぜかプログラムでは中間状態を無視してしまうような思考をするのは不思議ですね。

続・見ろ!!コードがゴミのようだ!!

Filed in ゴミコード, プログラム

せっかくなので先日書いたゴミサンプルをC++で書き直してみた。

#include <iostream>
#include <string>

class SampleClass {
public:
	int i;
	std::string s;
};

class Test {
	public:
		SampleClass* methodA(SampleClass* a, SampleClass* b);
};

int main(int argc, char* argv[])
{
	SampleClass a = SampleClass();
	a.i = 1;
	a.s = "a";

	SampleClass b = SampleClass();
	b.i = 2;
	b.s = "b";

	Test t = Test();
	
	std::cout << "== before ==" << std::endl;
	std::cout << "a= " << a.i << ":" << a.s << std::endl;
	std::cout << "b= " << b.i << ":" << b.s << std::endl;
	
	SampleClass* c = t.methodA(&a, &b);

	std::cout << "== after ==" << std::endl;
	std::cout << "a= " << a.i << ":" << a.s << std::endl;
	std::cout << "b= " << b.i << ":" << b.s << std::endl;
	std::cout << "c= " << c->i << ":" << c->s << std::endl;
	
	delete(c);
	return 0;
}

SampleClass* Test::methodA(SampleClass *a, SampleClass *b) {
	a->i = 11;
	a->s = "A";
	
	SampleClass b2 = SampleClass();
	b2.i = 12;
	b2.s = "B";
	b = &b2;

	SampleClass* c = new SampleClass();
	*c = b2;
	return c;
};

同じように作ったのだから結果は当然同じ。
ただ、戻り値cはnewしたものなので最後にdeleteしている。
この辺はJavaではそんなに意識しないことだろう。

== before ==
a= 1:a
b= 2:b
== after ==
a= 11:A
b= 2:b
c= 12:B

しかし超久しぶりにC++を書いたのでアクセス修飾子ってどうするんだっけ?と迷ってしまった。。
昔は当然と思っていたのに宣言と定義を分けるというのもかなり煩わしく感じた。
“System.out.println”と”std::cout <<" を比較すると"std::cout <<"などはプログラムを知らない人には謎の暗号にしか思えまい。。 久しぶりにC++のコードを書いて思ったのはポインタを使いたいとは思わないが、やはり概念は知っておくべきということ。 Javaで参照がどうとか勉強するよりもC++やCでポインタを学んだ方が近道な気がしている。 Javaにはガベッジコレクタがあると言ってもメモリの無駄な消費はメモリの断片化が起こってガベッジコレクタの頻発などパフォーマンスに悪影響です。インスタンスの無駄な生成自体もコストがかかりますしね。やはりJavaであってもいざというときはメモリを意識したプログラムを書くべきです。 こんなコード書いてもいいこと何もないですよ。 [java] SomeClass a = new SomeClass(); SomeClass b = new SomeClass(); // このインスタンスが作られてすぐ捨てられる b = a; [/java]

見ろ!!コードがゴミのようだ!!

Filed in ゴミコード, プログラム

まずは以下のjavaのコードをみてもらおう。

class SampleClass {
  public int i;
  public String s;
}

public class Test {
  static public void main(String[] args) {
    Test test = new Test();


    SampleClass a = new SampleClass();
    a.i = 1;
    a.s = "a";
    
    SampleClass b = new SampleClass();
    b.i = 2;
    b.s = "b";

    System.out.println("== before ===");
    System.out.println("a= " + a.i + ":" + a.s);
    System.out.println("b= " + b.i + ":" + b.s);

    SampleClass c = test.antiPattern(a,b);

    System.out.println("== after ===");
    System.out.println("a= " + a.i + ":" + a.s);
    System.out.println("b= " + b.i + ":" + b.s);
    System.out.println("c= " + c.i + ":" + c.s);
  }

  public SampleClass antiPattern(SampleClass a, SampleClass b) {
    a.i = 11;
    a.s = "A";

    SampleClass b2 = new SampleClass();
    b2.i = 12;
    b2.s = "B";
    b = b2;

    SampleClass c = new SampleClass();
    c = b2;
    return c;
  }
}

mainメソッドでantiPatternメソッドを呼ぶ前後で変数値を出力している訳だが何が出力されるか分かるだろうか。

結果はこうである。

== before ===
a= 1:a
b= 2:b
== after ===
a= 11:A
b= 2:b
c= 12:B

変数cは良いとしても、aやbまでもmainメソッドだけを見ていては決して分かりようがない結果が出力されるのである。さらに言うとantiPatternメソッドを見てみると直感的にはbも更新されてよさそうな気がするのだが実際はそうはならない。この辺はjavaのメソッドはポインタの値渡しであるという言語仕様によるものである。

しかしこれは言語仕様が許しているからと言って書いてはいけないコードの代表格と言えるだろう。mainメソッドを読んでいるのにantiPatternメソッドがどうなっているかまで意識しなくてはいけない。

上の例ではantiPatternメソッドが実行ステップにして10行足らずで完結しているのでまだよいが、antiPatternメソッドも同じように別のメソッドAを呼びだしてAの中でも渡した変数の中身が変わって返ってくるとなったらどうだろうか。さらにAの中でもBを呼び出し、Bの中でもCを呼び出し・・・。
結局全部のコードを理解しないと高々数十行のmainメソッドすら読み解くことができない。
これでは保守性が悪くてどうしようもない。
これをゴミコードと呼ばずにいられるだろうか。いや、いられまい(反語)。

さて、突然こんなことを書いたのは仕事でこんなコードを見たからである。
実際はもっと処理があるが、ポイントだけ抜粋した。

public class Y {
  public void methodA() {
    ・・・
    XListGenerator generator = new XListGenerator(a,b,c);
    List<X> xList = new ArrayList<X>();
    ・・・
    List<Something> somethingList = new ArrayList<Something>();
  ・・・
    generator.set(xList, somethingList);
  }
}

クラス名が悪い、変数名が悪いとか言いたいわけではない。
methodAが何をやっているか分かるだろうか。
「generatorに2つのlistを設定している。」と読みとるのが自然ではないだろうか。

しかしgenerator.set(list,somethingList)の中を見て愕然とした。以下のような内容なのであった。

   public set(List<X> xList, List<Something> somethingList) {
       for(i=0; i<somethingList.size(); i++) {
          String s= somethingList.get(i).getString();
          xList.get(i).setS(s);
       }
   }

なんなんでしょうかこれ。

完全に依存関係があるんだしメソッドを分けたって百害あって一利なしだ。setの主体もgeneratorなのかなんなのかよく分からない。
これでいいじゃない。

public class Y {
  public void methodA() {
    ・・・
    List<X> xList = new ArrayList<X>();
    ・・・
    List<Something> somethingList = new ArrayList<Something>();
  ・・・
    for(i=0; i<somethingList.size(); i++) {
       String s= somethingList.get(i).getString();
       xList.get(i).setS(s);
    }
  }
}

どうしてもgeneratorを使いたいなら、なるべく原型を残したとしてこうだ。

public class Y {
  public void methodA() {
    ・・・
  XListGenerator generator = new XListGenerator(a,b,c);
    ・・・
    List<Something> somethingList = new ArrayList<Something>();
  ・・・
    List<X> xList = generator.generate(somethingList);
  }
}

プログラムは動けばよい!というのは自分一人で作っている場合に限るのである。
こんなコードを読まなければいけない他人の身にもなってコードは書くべきである。