プログラマはどのように低水準なデータ型を使って高水準の概念を表現しているのか?

権藤研 夏ゼミ資料 2021/09/22
新山

0. 概要

1. 背景

よく「悪いコード」として primitive obsession と呼ばれる現象がある:

String username = getCurrentUserName();
String path = "/home/"+username+"/user.cfg";
// Unsafe path: extra check is needed!
File config = new File(path);

まともな (しかし面倒くさい) コード:

User user = getCurrentUser();
Path path = Paths.get(user.getHomeDirectory(), "user.cfg");
// Path is guaranteed to be safe.
File config = new File(path);

Primitive obsession はリスキーだが多くのプログラマが使っている。

1.1. Research Questions

  1. 多くのプログラマが使う「高水準な型 (abstract type, or Conceptual Type, c-type)」とはどんなものか?
  2. プログラマはそのような概念をコード中でどのように表現しているか?
  3. コードからそれを推論することは可能なのか?

2. 基本的なアイデア

プログラマは APIを呼び出すとき、その引数の用途 (c-type) を知っているはず:

String x = "foo/bar.txt";
var f = new java.io.File(x);  // x はパス名
したがってAPI呼び出しとその引数を調べれば、 プログラマが特定の概念をどのように表現しているかがわかるはずだ。

いくつかの Java オープンソースプロジェクトを調査し、 以下の 12 の c-type を定めることにした:

C-Type言語の型説明メソッド数
PATH String パス名 14
URL String URL/URI 4
SQL String SQL文 10
HOST String ホスト名 17
PORT int ポート番号 25
XCOORD int X座標 (GUI用) 25
YCOORD int Y座標 (GUI用) 25
WIDTH int 幅 (GUI用) 24
HEIGHT int 高さ (GUI用) 24
YEAR int 18
MONTH int 14
DAY int 18

これを選んだ基準は以下のとおりである:

  1. ほとんどのプログラマが理解できる明確な概念であること
  2. 互いにきっちり区別できること
  3. 広範囲のアプリケーションで使われていること

つぎに Java Standard API を調べて、 これらの c-type を含む計218メソッド (重複あり) を列挙した:

3. 実験

実験は以下の手順でおこなった:

  1. 中〜大規模の 25 のJava オープンソースプロジェクトを選ぶ。
  2. ソースコードを解析し、あらかじめ選んだ API 呼び出しを探す。
  3. それらの引数で使われている式を収集し、 その種別・大きさ・識別子に使われている単語などを調べる。

zookeeper/.../TxnLogToolkit.java:

    File file = new File(dir.getPath() + File.separator + Util.makeLogName(zxid)[PATH]);
    ...
jitsi/.../SIPCommSplitPaneDivider.java:

    rightButton.setBounds((insets.left * 2) + leftSize.width + rightSize.width[XCOORD],
                          y[YCOORD], rightSize.width[WIDTH], rightSize.height[HEIGHT]);

使用したプロジェクトとLoCは以下の通りである:

ProjectDescriptionLoC
hadoop 3.3.1 distributed computation 1,789k
ghidra 10.0 binary analyzer 1,588k
ignite 2.10.0 distributed database 1,165k
jetty 11.0.5 web container 441k
kafka 2.7.1 stream processing 384k
tomcat 8.5.68 web server 349k
jitsi 2.10 video conference 327k
binnavi 6.1.0 binary analyzer 309k
netty 4.1.65 network library 303k
libgdx 1.10.0 game framework 272k
alluxio 2.5.0-3 data orchestration 228k
plantuml 1.2021.7 UML generator 210k
grpc 1.38.1 RPC framework 195k
jenkins 2.299 automation 177k
jmeter 5.4.1 network analyzer 145k
jedit 5.6.0 text editor 125k
gephi 0.9.2 graph visualizer 120k
zookeeper 3.7.0 distributed computation 114k
selenium 3.141.59 browser automation 91k
okhttp 4.9.1 HTTP client 36k
jhotdraw 7.0.6 graph drawing 32k
arduino 1.8.15 development environment 27k
gson 2.8.7 serialization framework 25k
websocket 1.5.2 network framework 15k
picasso 2.8 image processing 9k
jpacman action game 3k
Total8,480k

各プロジェクト・c-typeごとの抽出された式の数は以下のとおりである。 出現する式の数は、プロジェクトの大きさにほぼ比例する。 しかし c-typeの種類はプロジェクトによって偏っている。

ProjectLoCPATHURLSQLHOSTPORTXCOORDYCOORDWIDTHHEIGHTYEARMONTHDAYOTHERAll
alluxio 228k 72 12 0 22 26 0 0 0 0 15 15 15 51 228
arduino 27k 39 5 0 12 9 6 6 42 42 0 0 0 1 162
binnavi 309k 42 7 1 2 1 23 23 67 67 1 0 0 1 235
gephi 120k 5 0 2 0 0 28 28 66 66 0 0 0 0 195
ghidra 1,588k 369 25 0 10 8 320 320 511 511 13 13 13 13 2,126
grpc 195k 33 16 0 68 71 0 0 0 0 0 0 0 75 263
gson 25k 0 4 0 2 0 0 0 0 0 6 6 6 7 31
hadoop 1,789k 978 634 9 288 259 0 0 0 0 2 2 2 124 2,298
ignite 1,165k 168 85 666 106 111 0 0 0 0 12 12 12 101 1,273
jedit 125k 130 19 0 3 3 50 50 112 112 0 0 0 3 482
jenkins 117k 82 28 0 6 6 1 1 1 1 102 102 102 237 669
jetty 441k 72 216 9 163 104 0 0 0 0 0 0 0 37 601
jhotdraw 32k 6 5 0 1 1 96 96 50 50 0 0 0 1 306
jitsi 327k 22 18 1 8 31 78 78 234 234 0 0 0 30 734
jmeter 145k 112 62 2 31 28 7 7 62 62 0 0 0 28 401
jpacman 3k 0 0 0 0 0 0 0 1 1 0 0 0 0 2
kafka 384k 37 1 0 85 72 0 0 0 0 14 14 14 44 281
libgdx 272k 83 7 0 4 5 7 7 36 36 0 0 0 0 185
netty 303k 38 24 0 54 130 0 0 0 0 1 1 1 117 366
okhttp 36k 2 24 0 6 7 0 0 0 0 0 0 0 3 42
picasso 9k 1 0 0 0 0 0 0 0 0 0 0 0 0 1
plantuml 210k 29 4 0 5 11 4 4 11 11 2 2 2 2 87
selenium 91k 44 66 0 15 12 1 1 0 0 1 1 1 21 163
tomcat 349k 207 64 22 38 52 0 0 10 10 0 0 0 47 450
websocket 15k 9 44 0 5 43 0 0 1 1 0 0 0 1 104
zookeeper 114k 88 4 0 126 192 0 0 1 1 0 0 0 47 459
Total8,480k 2,668 1,374 712 1,060 1,182 621 621 1,205 1,205 169 168 168 991 12,144

以下は抽出した式に含まれていた PATH, URL, XCOORD, WIDTH 表現の抜粋である:

PATHTop Expressions
alluxiopath, mLocalUfsPath+ufsBase, base
arduinopath, PreferencesData.get("runtime.ide.path")
binnavifilename, directory, pathname
gephiSystem.getProperty("netbeans.user")
ghidragetTestDirectoryPath(), path, filename
grpcuri.getPath()
hadoopGenericTestUtils.getRandomizedTempPath()
ignitepath, U.defaultWorkDirectory(), fileName
jeditpath, dir, directory
jenkinsSystem.getProperty("user.home"), war
jettyfile.getParent()
jhotdrawprefs.get("projectFile", home)
jitsipath, localPath
jmeterfilename, path, file
kafkastoreDirectoryPath, argument
libgdxname, sourcePath, imagePath.replace('\textbackslash\textbackslash','/')
nettygetClass().getResource("test.crt").getFile()
plantumlfilename, newName
seleniumSystem.getProperty("java.io.tmpdir"), logName
tomcatpathname, path, docBase
zookeeperpath, KerberosTestUtils.getKeytabFile()
URLTop Expressions
alluxiojournalDirectory, folder, inputDir
arduinocontribution.getUrl(), packageIndexURLString
binnaviurl, urlString
ghidraref, getAbsolutePath(), url.toExternalForm()
grpctarget, TARGET, oobTarget
gsonnextString, urlValue, uriValue
hadoopuri, url, s
igniteGridTestProperties.getProperty("p2p.uri.cls")
jeditpath, str, fileIcon
jenkinsurl, site.getData().core.url, plugin.url
jettyuri, inputUrl.toString(), s
jitsiurl, imagePath, sourceString
jmeterurl, LOCAL_HOST, requestPath
kafkaconfig.getString(METRICS_URL_CONFIG)
libgdxurl, URI, httpRequest.getUrl()+queryString
nettyURL, request.uri(), server
seleniumurl, baseUrl, (String)raw.get("uri")
tomcaturl, location, path
websocketuriField.getText(), uriinput.getText()
zookeeperurlStr
XCOORDTop Expressions
arduinonoLeft, cancelLeft
binnavix, m_x
gephicurrentMouseX, x, bounds.x
ghidrax, center.x+deltaX, filterPanelBounds.x
jeditx, event.getX(), leftButtonWidth+leftWidth
jhotdrawevt.getX(), x, e.getX()
jitsix, button.getX(), dx
jmetergraphPanel.getLocation().x, cellRect.x, x
libgdxupButtonX, getWidth()-buttonSize.width-5, x
plantumle.getX()
WIDTHTop Expressions
arduinowidth, imageW, Preferences.BUTTON_WIDTH
binnaviCOLORPANEL_WIDTH, TEXTFIELD_WIDTH, width
gephiw, constraintWidth, DEPTH
ghidrawidth, center.width, filterPanelBounds.width
jeditwidth, buttonSize.width, colWidth
jhotdrawframeWidth, r.width, bounds.width
jitsiMAX_MSG_PANE_WIDTH, WIDTH, width
jmetergraphPanel.width
libgdxwidth, buttonSize.width
plantumlnewWidth
tomcatWIDTH

各c-typeにおける、複雑な式と演算子の組合せ例は以下のとおりである:

C-TypeExpressions
PATH
mLocalUfsPath + ufsBase
selectedFile.getAbsolutePath() + PREFERENCES_FILE_EXTENSION
dir.getPath() + DIR_FAILURE_SUFFIX
U.defaultWorkDirectory() + separatorChar + DEFAULT_TARGET_FOLDER + separatorChar
URL
url.toExternalForm().substring(GhidraURL.PROTOCOL.length() + 1)
str + KMSRESTConstants.SERVICE_VERSION + "/"
newOrigin(getScheme(),getHost(),getPort()).asString() + path
base + configFile
XCOORD
center.x + center.width
leftButtonWidth + leftWidth
evt.getX() - getInsets().left
prefs.getInt(name+".x", 0)
WIDTH
Math.max(contentWidth, menuWidth) + insets.left + insets.right
TITLE_X_OFFSET + titlePreferredSize.width
width + insets.left + insets.right + 2
(int)(bounds.getWidth() * percent)

抽出した式の長さは以下のとおりである (nは式中の項の数を表す):

C-Typen=1n=2n=3n=4n=5n=6n≥7
PATH 49.6%22.8%7.0%6.3%4.8%2.2%7.3%
URL 31.6%18.5%13.7%13.5%10.2%5.8%6.8%
SQL 47.5%12.1%8.7%3.1%4.4%5.5%18.8%
HOST 59.2%11.5%3.0%22.1%2.0%0.1%2.1%
PORT 68.4%27.5%2.1%1.2%0.3%0.4%0.0%
XCOORD54.1%24.6%9.8%6.4%1.6%2.1%1.3%
YCOORD52.5%22.4%10.1%9.0%2.6%1.9%1.4%
WIDTH 71.0%15.2%6.3%2.5%1.5%1.8%1.7%
HEIGHT71.4%15.4%6.4%3.1%1.5%1.3%1.0%
YEAR 96.4%2.4%1.2%0.0%0.0%0.0%0.0%
MONTH 79.8%19.6%0.6%0.0%0.0%0.0%0.0%
DAY 99.4%0.0%0.6%0.0%0.0%0.0%0.0%

4. C-type の推論

次に、式の表現のみから c-type を推論できるかどうかを考えてみる。 今のところ、我々は c-type の判定は API 呼び出しに頼っている。 もしこれができれば API呼び出し以外の部分の c-type も判定することができる。

基本的な戦略は以下のとおりである:

  1. 抽出した c-type とその式 (12k) を訓練データ・テストデータとして使用する。
  2. 各式から分類に使えそうな特徴量を抽出する。
  3. 機械学習 (決定木) を使って、どれくらいうまく判定できるかを測定する。

機械学習アルゴリズムとして決定木を使ったのは、 記号的な特徴に向いており、かつ、できあがった規則が人間に読みやすいためである。

まず、式に出てきた識別子のうち、頻出の単語を取り出すと 以下のようになる:

C-TypeTop words (# Projects)
PATHget (21), path (21), file (20)
URLurl (19), get (18), string (18)
SQLget (6), query (5), create (3)
HOSThost (21), get (17), address (17)
PORTport (22), get (18), local (10)
XCOORDwidth (9), x (9), get (9)
YCOORDheight (9), y (9), get (8)
WIDTHwidth (13), get (11), size (10)
HEIGHTheight (12), get (11), size (10)
YEARyear (4), get (2), int (2)
MONTHjanuary (3), month (3), december (3)
DAYday (3), int (2), parse (2)

本手法では、式の特徴を表現するのにデータフロー図を使った。 式を構文解析して項どうしの依存関係を抽出する。 たとえば new File(config.getPath(i)) のデータフロー図は 以下のようになる:

new File( ) config i getPath() Secondary identifiers Primary identifier
new File(config.getPath(i)) のデータフロー図

各式に対して、データフローから "primary identifier" および "secondary identifier(s)" を 特徴量として使用して学習する。 以下のような規則を使うと、以下の素性が抽出できる:

ExpressionDependency
# (constant) #
A (variable access) A
A.B (field access) A → B
B(A) (method call) A → B()
A.B() (instance method call) A → B()
op A (applying a unary operator) A → op
A op B (applying a binary operator) A → op, B → op
B = A (assignment) A → B

これらの素性を使って、各ctypeを判別可能かどうか実験した。 ここでは評価にプロジェクトごとの leave-one-project-out の クロスバリデーションを使っている:

以下のような結果が得られた:

C-TypePrecisionRecallF-score
PATH 68.9% 91.8% 78.8%
URL 61.3% 53.0% 56.8%
SQL 70.4% 80.6% 75.2%
HOST 70.0% 73.8% 71.8%
PORT 84.6% 87.5% 86.0%
XCOORD 95.7% 82.1% 88.3%
YCOORD 97.5% 79.4% 87.5%
WIDTH 92.0% 92.5% 92.2%
HEIGHT 90.4% 93.4% 91.9%
YEAR 100.0% 83.7% 91.1%
MONTH 100.0% 77.0% 87.0%
DAY 100.0% 61.1% 75.9%
Average 85.9% 79.6% 82.7%

URL c-type は性能が悪い。 その理由は、URLの式は

"https://"+getHostName()+":"+getPort()+"/"+getPath()
などのように長くなる傾向があり、 さらに内部で Hostname や port など他の c-type と混同するような 項が多く表れるためであった。

5. 結論

本研究では、Java standard API で使われている 12 の c-type を定義し、 それらが 26個のオープンソースプロジェクトでどのように使われているかを分析した。 ソースコード中の API 呼び出しを収集してそれらの傾向を調べた。 その後、データフロー解析と決定木を組み合わせた c-type 判定手法をテストした。

Research Question ふたたび:

  1. 多くのプログラマが使う「高水準な型 (abstract type, or Conceptual Type, c-type)」とはどんなものか?
    → 12個のc-typeを定義した。実験によれば、それらの出現頻度はプロジェクトのドメインに依存していた。
  2. プログラマはそのような概念をコード中でどのように表現しているか?
  3. コードからそれを推論することは可能なのか?
    → これらの質問は関連している。比較的単純な決定木ベースの手法が うまくいっているので、少なくともよく知られているコンセプトに対しては、 プログラマは c-type を判定しやすいように書いていると思われる。

5.1. Threats to Validity


Yusuke Shinyama