SlideShare a Scribd company logo
互換性とコード進化の両立
川口耕介 CloudBees, Inc.
kk@kohsuke.org / @kohsukekawa

©2013 CloudBees, Inc. All Rights Reserved

1
分散・独立・並行開発

©2013 CloudBees, Inc. All Rights Reserved

2
http://commons.wikimedia.org/wiki/File:Close_up_of_Hand_Cut_Jigsaw_Puzzle.JPG
©2013 CloudBees, Inc. All Rights Reserved

3
実行時結合
©2013 CloudBees, Inc. All Rights Reserved

4
共通ライブラリ

ウェブアプリX

モジュールA

モジュールB

ライブラリ v1.0

ライブラリ v2.0

©2013 CloudBees, Inc. All Rights Reserved

5
©2013 CloudBees, Inc. All Rights Reserved

6
©2013 CloudBees, Inc. All Rights Reserved
© いまいまさのぶ

7
プラグイン機構
プラグイン

プラグイン

コア

プラグイン

プラグイン

様々なクラス

©2013 CloudBees, Inc. All Rights Reserved

…

80+のライブラリ

8
拡張ポイント
• コア

interface Animal {
void bark();
}

• プラグイン
@Extension
class Dog implements Animal {
void bark() { print(“ワン!”); }
}
©2013 CloudBees, Inc. All Rights Reserved

9
プラグインはコアをガンガン使う

Project p = …;
Future<Build> f = p.scheduleBuild();
Build b = f.get();

©2013 CloudBees, Inc. All Rights Reserved

10
この苦しみから解脱する方法はないかと
• お釈迦様の絵
• 徐々に蓄積してきたノウハウ・ツールを大公
開

©2013 CloudBees, Inc. All Rights Reserved

11
©2013 CloudBees, Inc. All Rights Reserved

mage © http://sfcitizen.com/blog/wp-content/uploads/2011/11/6302790910_c4eb865892_o-copy.jpg

12
フィールドの隠蔽

class Point {
int x,y;
}

class Point {
int getX();
int getY();
void setX(int);
void setY(int);
}

©2013 CloudBees, Inc. All Rights Reserved

13
インターフェース?抽象クラス?

interface Animal {
void bark();
}

abstract class Animal {
public abstract
void bark();
}

©2013 CloudBees, Inc. All Rights Reserved

14
インターフェース?抽象クラス?

interface Animal {
void bark();
void bark(int times);
}

©2013 CloudBees, Inc. All Rights Reserved

15
インターフェース?抽象クラス?

abstract class Animal {
public abstract void bark();
public void bark(int times) {
for (int i=0; i<times; i++)
bark();
}
}

©2013 CloudBees, Inc. All Rights Reserved

16
ピンポンパターン

abstract class Animal {
@deprecated
public void bark() {
bark(1);
}
public void bark(int times) {
for (int i=0; i<times; i++)
bark();
}
}

©2013 CloudBees, Inc. All Rights Reserved

17
コンストラクタの肥大化
• 外部コードによる
サブクラス化を許可
したい
• GitSCMの例

public GitSCM(
String scmName,
List<UserRemoteConfig> userRemoteConfigs,
List<BranchSpec> branches,
UserMergeOptions userMergeOptions,
Boolean doGenerateSubmoduleConfigurations,
Collection<SubmoduleConfig> submoduleCfg,
boolean clean,
boolean wipeOutWorkspace,
BuildChooser buildChooser, GitRepositoryBrowser browser,
String gitTool,
boolean authorOrCommitter,
String relativeTargetDir,
String reference,
String excludedRegions,
String excludedUsers,
String localBranch,
boolean disableSubmodules,
boolean recursiveSubmodules,
boolean pruneBranches,
boolean remotePoll,
String gitConfigName,
String gitConfigEmail,
boolean skipTag,
String includedRegions,
boolean ignoreNotifyCommit,
boolean useShallowClone) {

©2013 CloudBees, Inc. All Rights Reserved

18
我慢してセッター
class Foo {
void initXYZ(int x, int y, int z) { … }
void initABC(String a, String b, String c) { … }
}

©2013 CloudBees, Inc. All Rights Reserved

19
コンフィグパターン
class FooConfig {
int x,y,z;
String a,b,c;
}

abstract class Foo {
Foo(FooConfig config) {
…
}
}

©2013 CloudBees, Inc. All Rights Reserved

20
©2013 CloudBees, Inc. All Rights Reserved

mage © http://sfcitizen.com/blog/wp-content/uploads/2011/11/6302790910_c4eb865892_o-copy.jpg

21
パッケージ越しにアクセスするた
めにpublicなんだけど、
外部からは使わせたくない

©2013 CloudBees, Inc. All Rights Reserved

22
互換性用に@deprecatedとした
コードを次のステップに進めたい

©2013 CloudBees, Inc. All Rights Reserved

23
だ をつ 修 ア 俺
し く 飾ク俺
て る 子セ
! 道 をス
具

© 小学館

©2013 CloudBees, Inc. All Rights Reserved

24
http://kohsuke.org/access-modifier/
• 独自のアクセスチェッカを定義する
class 俺俺アクセス extends AccessRestriction
{
public void instantiated(loc,target,listener) {
listener.onError(null,loc,
target+“インスタンス生成は禁止”);
}
}
• サブタイプ、読み、書き、呼び出しなど6種
類
©2013 CloudBees, Inc. All Rights Reserved

25
http://kohsuke.org/access-modifier/
• 通常のアクセス修飾子と共に使う
@Restricted(俺俺アクセス.class)
public class Foo {
public String value;
}
• Mavenプラグインがクラスファイルを検査

©2013 CloudBees, Inc. All Rights Reserved

26
©2013 CloudBees, Inc. All Rights Reserved

27
バイナリ互換性を活用
interface Animal {
void bark();
}
class Dog implements Animal {
void bark() { print(“ワン”); }
}

Animal a = new Dog();
a.bark();
©2013 CloudBees, Inc. All Rights Reserved

28
バイナリ互換性を活用
interface Animal {
void bark(int n);
}
1. new Dog()でInstantiationError
class Dog implements Animal{
2. Dog→Animalのキャストで
ClassCastException
void bark() { print(“ワン”); }
3. a.bark(3)でAbstractMethodError
}
4. その他

Animal a = new Dog();
a.bark(3);
©2013 CloudBees, Inc. All Rights Reserved

29
try {
a.bark(3);
} catch (AbstractMethodError e) {
// 互換性モード
…
}

©2013 CloudBees, Inc. All Rights Reserved

30
ジェネリクス
class Foo {
List<Object> getChildren() { … }
}

class Foo {
List<String> getChildren() { … }
}

©2013 CloudBees, Inc. All Rights Reserved

31
ジェネリクス
class Foo<T extends X> implements Future<T> {
T get() { … }
}

class Foo<T extends Y> implements Future<T> {
T get() { … }
}
class Y extends X { … }
©2013 CloudBees, Inc. All Rights Reserved

32
ジェネリクス型変更のルール
• メソッド・フィールドのErasureが変わって
いなければOK
– T<X,Y,…>
→
T
– T extends X →
X
–…

• 検査してくれるツールをいつかは書きたい

©2013 CloudBees, Inc. All Rights Reserved

33
根底のルール
foo/Foo.class
invokevirtual
org.example.Bar#method1(java.lang.String,int,int)boolean
…

org/example/Bar.class

メソッド method1 (java.lang.String,int,int):boolean

©2013 CloudBees, Inc. All Rights Reserved

34
参照解決のルール
• メソッドへの参照
– クラス名:
– メソッド名:
– 戻り値型:
– パラメータ型:

java.lang.String
indexOf
int
java.lang.String,int

• 関連事項
– アクセス修飾子
– 例外

©2013 CloudBees, Inc. All Rights Reserved

35
コード進化のパターン
class Foo {
X get() { … }
}

class Foo {
Y get() { … }
}
class Y extends X { … }
©2013 CloudBees, Inc. All Rights Reserved

36
コード進化のパターン
class Foo {
X get() { … }
}

class Foo {
Y get() { … }
X get() { … }
}
©2013 CloudBees, Inc. All Rights Reserved

37
Bridge Method Injectorプロジェクト bit.ly/b-mi
class Foo {
@WithBridgeMethods(X.class)
Y get() { … }
}

class Foo {
Y get() { … }
X get() { Y y=get(); return y; }
}
©2013 CloudBees, Inc. All Rights Reserved

38
応用
class Foo {
@WithBridgeMethods(value=X.class,
castRequired=true)
Object get() { … }
}
class Foo {
Object get() { … }
X get() { Object o=get(); return (X)o; }
}
©2013 CloudBees, Inc. All Rights Reserved

39
自分はルールを守っていても
モジュールB

ライブラリ 1.0

モジュールA

ライブラリ 2.0

©2013 CloudBees, Inc. All Rights Reserved

40
シェーディング / パッケージ・リネーミング
package org.jenkinsci.foo;
import org.acme.A;
import org.acme.B;
class Foo {
private List<A> a = …;
void bar() { new B().b(); }
}
©2013 CloudBees, Inc. All Rights Reserved

41
シェーディング / パッケージ・リネーミング
package org.jenkinsci.foo;
import hidden.org.acme.A;
import hidden.org.acme.B;
class Foo {
private List<A> a = …;
void bar() { new B().b(); }
}
©2013 CloudBees, Inc. All Rights Reserved

42
シェーディング / パッケージ・リネーミング
• 事後に
– maven-shade-plugin

• 事前に
– 予めリネームしたやつをjarにパッケージしてお
く

©2013 CloudBees, Inc. All Rights Reserved

43
うまくいかない場合もある
• 文字列操作でパッケージ名をいじっている
• META-INF/…
• モジュールの公開APIから参照されている

©2013 CloudBees, Inc. All Rights Reserved

44
©2013 CloudBees, Inc. All Rights Reserved

mage © http://sfcitizen.com/blog/wp-content/uploads/2011/11/6302790910_c4eb865892_o-copy.jpg

45
ありがちなパターン
class Foo {
static final Foo INSTANCE = new Foo();
…
}
class Bar {
void bar() {
doSomethingWith(Foo.INSTANCE);
}
}
©2013 CloudBees, Inc. All Rights Reserved

46
逆立ちしたって無理!
class Foo {
static Foo getInstance() { … }
}

class Bar {
void bar() {
doSomethingWith(Foo.INSTANCE);
}
}

©2013 CloudBees, Inc. All Rights Reserved

47
©2013 CloudBees, Inc. All Rights Reserved

48
書き換え方
class Foo {
@AdaptField(name=“INSTANCE”)
static Foo getInstance() { … }
}

Barのロード時に
書き換え

class Bar {
void bar() {
doSomethingWith(Foo.INSTANCE);
}
}
©2013 CloudBees, Inc. All Rights Reserved

49
Bytecode Compatibility Transformer bit.ly/b-c-t
class Foo {
@AdaptField(name=“INSTANCE”)
static Foo getInstance() { … }
}
foo.jar

Foo.class

変換
定義

©2013 CloudBees, Inc. All Rights Reserved

50
Bytecode Compatibility Transformer bit.ly/b-c-t
• 実行時
– 変換定義 → byte[] transform(byte[] classFile)

独自class loader

foo.jar

bar.jar

変換
定義

©2013 CloudBees, Inc. All Rights Reserved

51
思わぬ落とし穴が!

©2013 CloudBees, Inc. All Rights Reserved

52
思わぬ落とし穴
class Foo {
static Foo INSTANCE;
}
class Bar extends Foo {
void m() {
System.out.println(INSTANCE);
}
Bar#INSTANCE
}
©2013 CloudBees, Inc. All Rights Reserved

53
プログラム変換の利点
• 思い切った書き換え
• 予期しない変更に対応

©2013 CloudBees, Inc. All Rights Reserved

54
プログラム変換の欠点
• 独自のクラスローダが必要

©2013 CloudBees, Inc. All Rights Reserved

55
http://commons.wikimedia.org/wiki/File:Light_Bulb.jpg
©2013 CloudBees, Inc. All Rights Reserved

56
一分で学ぶInvokedynamic
• Java7の新しい機能
• 実行時リンク
• 静的リンクと同じ速度
void foo() {
+リンカの為の追
int x = 5;
加情報
String y = “hello”;
Object o = intとstringからObjectを返す何か(x,y)
return o;
}
+リンカの名前
©2013 CloudBees, Inc. All Rights Reserved

57
http://no-more-tears.kohsuke.org/

class Foo {
void foo() {
Project p = new Project();
Future<Build> f = p.scheduleBuild();
Build b = f.get();
…
}
}

©2013 CloudBees, Inc. All Rights Reserved

58
ビルド時にinvokedynamicに置き換え
new Project()

class Foo {
Project.scheduleBuild()
void foo() {
Project p = [void→Project](this);
Future<Build> f = [Project→Future] (p);
Build b = [Future → Build](f);
…
Future.get()
}
}

©2013 CloudBees, Inc. All Rights Reserved

59
実行時に適宜書き換え

class Foo {
void foo() {
Project p = Project.create();
Future<Build> f = p.scheduleBuild(0);
Build b = f.value;
…
}
}

©2013 CloudBees, Inc. All Rights Reserved

60
まとめ

互換性とコードの進化は両立できる

©2013 CloudBees, Inc. All Rights Reserved

61

More Related Content

コードの互換性と進化の両立

Editor's Notes

  • #7: 本当に実行するまでわからない
  • #8: Jenkinsでも似たような問題に何度も遭遇した
  • #11: でも進化しないといけない、どうするか。
  • #12: コードの進化と互換性の狭間の苦しみからどう解脱するか。徐々に蓄積してきたノウハウ・ツールを大公開
  • #28: コンパイルはできないが動く。ソースコード上の規則とバイナリ上の規則の違い。
  • #49: プログラムを書き換えればいいじゃんか