クフでダローバルな日記

タフでもグローバルもない

東京大学eeic3年後期実験「大規模ソフトウェアを手探る」2015年度まとめ

この記事はeeicアドベントカレンダーの2日目の記事です。
と言っても、eeicアドベントカレンダーは12/2に突発的に始めたので、これが最初の記事になります。

目次

続きを読む

挫折してプログラミングに思うこと

突然ですが、最近プログラミングや今後の人生について考える機会が多かったので、今後の自分のためにまとめておこうと思います。 酒を飲みがてら。ジンジャーエールおいちい。

いつもとは違って人に読ませるためのものではないので、kuhudaro.mdに投稿しようか迷ったのですが、ちゃんと文章にしようと思いこちらに投稿することにしました。

契機となったはてなインターン

この話を何度もするのは鬱陶しいかもしれないが、今年の夏はてなインターンに行ったことは、やはり自分の人生でもかなりの契機になったと思う。
あのインターンで僕は、他大でプログラミングを生業としようとしている同年代の人達と、遅ればせながら初めて交流した。
もちろん、今までにも少しは交流する機会はあったけれど、あそこまでプログラミングのことだけを考えた1ヶ月はなかったし、本当に貴重な体験をさせて戴けたと思っている。

同年代の彼らとの交流はカルチャーショックから始まったように記憶している。

僕にとっての「プログラミング」は「自分が作りたいものを作る/自己承認欲求を満たす手段」であったし、それ故まず「作りたいもの」があって、その実現のために技術を習得するというトップダウンなものばかりだった。
しかしこのトップダウン的な勉強には、当時から気づいていた大きな欠点がある。 習得する知識の幅が狭いのだ。
例えば、twitterBOTを作るためにはruby,paas,cron,APIなどの知識が必要であり、webアプリを作るのであればフレームワークやhtml・css・jsなどの知識が当然必要になってくる。
しかし、逆に言うとそれさえマスターしてしまえば、すこし発展させたものはごく簡単に作れるようになってしまう。
この簡便さは一見良いことのように思えるが、実のところ全く勉強にならない。全くのコピペプログラミングでしかないのに成果物が生み出せるため、知識の幅が広がるペースが極めて遅いのである。

一方、はてなインターンで出会った彼らは、プログラミングの基礎となる知識がとても豊富だった。
それは競技プログラミングについてもそうだし、扱える言語の数についても僕は全く比べ物にならないレベルだった。
彼らと話していると常に僕は知識の乏しさに恥じ、少しバズッた小さな小さなwebアプリやtwitterBOTをさも代表作のように誇示していた視野の狭さを悔やんだ。

ただ、はてなインターンのマンガチームでそれなりの成果を挙げられたことは、少しばかりの自信につながった。 というより、そういった誉がなかったら僕は完全に挫折していただろうと思う。

夏以降の僕

はてなインターン以降は、自分の課題であるボトムアップ的で幅の広い勉強をしたいと思っていた。
また、視野を拡げるために機会があれば自分から行動するようになった。

そこでまず、「モノ」を作るハッカソンのHackUに出ることに決めた。
このHackUでも、二つのショックを受けた。
一つは多摩美大の学生たちの「プロフェッショナルさ」であり、一つは同じ大学の優秀な同期の存在である。

この東大×多摩美のHackUでは、僕と多摩美の学生三人がチームとなってものを作った。
僕は電気系にいながら電子工作をした経験が殆ど無いので、結局完成度の高いものを作ることはできなかった。 より厳密に言うならば、僕の失敗のせいで全く機能しないモノとなってしまった。
これに関しては僕が自分を責めれば良いだけなのだが、ガワを作った多摩美の学生は、僕に対して適切な指示と交渉をしてくる能力、ものを作るスピードと精密さ、そしてもちろんデザイン性・機能性と、凡そ同い年とは思えないフルスタックな人物だった。
さらに、このHackUで優勝した同じ東大工学部の学生も、ものを作るスピードやデザイン性やアイディアの豊かさにおいて僕を圧倒していた。

要するに、夏にかろうじて自分を支えていたトップダウン的な能力も音を立てて崩れ去ったのである。

それ以降、僕は自分の得意なものを見つけるため、色々なものに手を出した。
競技プログラミングやctf、人工知能の勉強やIT起業との就活イベントなど、目につく物は手当たり次第力を注ごうとして、その殆どで挫折した。
唯一情報可視化技術だけはとても興味を持っているが、これも時間の問題なのではないかと思う。

というのも、そもそも「プログラミング」自体が出来ないのだということに気づいたためである。

作りたいものを作っていた時、本来プログラミングに必要な可読性、保持性、拡張性などは殆ど無視していた。
そんなものはなくても、それなりのものは出来る。

逆に、自分が思いつくもの・作りたいものはすべて実現できるという自信もある。
随分楽天家のように聞こえるかも知れないが、実際は悲観的な自信である。 すなわち、自分が実現できる範囲のものしか、自分では思いつけないのだ。 (実際、僕の場合webサービスのアイディアは思いつくこともあるが、電子工作やネイティブアプリのアイディアは殆ど浮かばない。)

そうなってくると、逆に作りたいものをすべて作っていたら時間が無くなってしまうし、上記に上げたような必須能力は身につかないままだろう。
だからこそ、上記に言ったような「プログラミング」の方法論を身に付けたいと感じている。

はてなドワンゴ合同ハッカソンとこれから

昨日(2015/11/28)、はてなドワンゴの合同ハッカソンに出場した。 作成したものについては今度また別に記すつもりだ。
僕はほとんど優勝する気はなく、久しぶりにただ自分の作りたいものを、馬の合う友人と作ることが出来て純粋に楽しかった。

ただ、やはりここでも自分が到底かなわないような技術力を持つ人達を目の当たりにしたし、それなりに落ち込んだりもした。

しかし、最近はむしろ、そういった技術力を磨くのではなく、純粋に研究をする方がやはり僕には向いているのではないかと思い始めている。 僕はやはり勉強は好きだし、世の中に数多いる自分より優秀なエンジニアと同じ土俵で戦う必要はないのではないかと感じたためである。
もちろん、研究の世界でも自分より優秀な人はいくらでもいるだろうし、彼らに打ちのめされる自分は既に想像できる。
それでも、(この比喩は正しくないかもしれないが)パーツを組み合わせて完成品を作るよりも、パーツとなるもの、さらにはそのパーツを創りだすものというより基礎的な部分の研究に、一工学徒として魅力を感じている。

実際に研究室に配属されるのはまだ先で、研究室を選ぶのにもかなり苦労しているが、この情熱が冷めないように、「ボトムアップ」を意識したプログラマ生活を送って行きたい。

「シンデレラ」への反省としての「イントゥ・ザ・ウッズ」

久々にプログラミング以外のことを書いてみようと思います。

先日、ディズニーの映画「イントゥ・ザ・ウッズ」を観ました。 特に期待せずに見たのですが、結構面白かった上にあまりweb上に感想エントリもあんまり上がっていないようだったので、感想を書いてみようと思います。

普段ディズニーアニメをそんなに見ているわけではないので、もし間違ってる所があればコメントとかで指摘して戴けると幸いです。

紹介エントリというよりは感想文(考察?)的な感じなので当然ネタバレを多数含みますが、その点はご容赦ください。

ちなみに本当は一回で書ききる予定だったのですが、なんか長くなっちゃったので分割してます。

続きを読む

gitに新機能を追加してgit masterを目指す(応用編)

前回の記事の続きとなります。
今回はcommitに新機能を追加していきます。

前回まで

前回、git addを手探って、git add .とするように変更しました。

前回記事のコメントでも指摘されたように、このような変更は簡単にプラグインで対応できます。
従って、今回の変更点はプラグインやシェルの設定では実現しがたいものとなるようにします。

変更内容

git commit --amend をしようとした際に、直前のcommitがpush済みであったら中止する

目的

この変更の必要性がわかりづらいと思われるので、丁寧に説明してみます。

git commit --amendは、直前のcommitを修正できるコマンドです。
commit messageなどを修正したい時には便利なのですが、もし既にpushしたcommitを修正しようとすると面倒なことになります。

例えば直前のcommit A がpush済みであるとします。
この時、remoteにも A が存在します。

この状態でAをamendし、A'とするとしましょう。

そうすると、pushした際にremoteでAとA'がconflictするため、修正がかなり面倒なことになってしまうわけです。

実際、gitのマニュアルでも、以下の様な強い口調で、push済みのcommitを修正することが非難されています。

公開リポジトリにプッシュしたコミットをリベースしてはいけない

この指針に従っている限り、すべてはうまく進みます。もしこれを守らなければ、あなたは嫌われ者となり、友人や家族からも軽蔑されることになるでしょう。
Git - リベース

また、gitのメーリングリストでもこの問題については既に話し合われていたようで、SmallProjectsIdeas - Git SCM Wiki に以下の様な記述がありました。

Commands like "git rebase", "git rebase -i", "git reset", "git filter-branch", "git commit --amend" can be very powerful to rewrite local history before publishing it. On the other hand, they can be very dangerous if used on an already published history. Git could relatively easily detect that one is rewriting a commit that is an ancestor of a remote-tracking branch, and warn the user (perhaps giving a way to "git reset --hard" back to the original state).

以上のことから、commitをamendする際に、それがpush済みであるか否かを判別するのが必要であるということがわかると思います。

手探りの手順

手探りの手順、といってもだいたいのことは前回のadd編と同じです。

しかし、今回の最大の問題点は 「あるcommitがpushされているのかどうかをいかにして判別するか?」 というものだったので、以下の様な手順を踏みました。

  1. localのcommitとremoteのcommitの関連性を探す
  2. 全探索
  3. git statusを手探る

というものです。以下順に解説していきます。

localのcommitとremoteのcommitの関連性を探す

git pushした際に、「pushした」という情報がどこかに保存されていれば、そのフラグを見ることで実現できるだろうと予想して、まずはこの関連性を探すところから始めました。

具体的には、最初にgit pushの動きを追いました。 もし「pushした」という情報をどこかに保存するのであれば、その処理はpushをした際になされるだろうという予測をした為です。
しかし、pushの際には処理が多くていまいち全容が把握できず、多分そういった情報は保存していないだろうという予測はできたのですが、確信を持てずにいました。
この原因はcommitの仕組みについても殆ど理解していなかったことにあると考え、一度git addgit commitgit pushが何をやっているのかをボトムアップ式に学んでみることにしました。

主に参考にしたのはGit - Gitの内側 と、Git の仕組み (1) - こせきの技術日記です。

前者は公式の情報でありかなり詳細なのですが、gitは用語が難しく、概念としてイメージしづらいという難点があります。
そこで、後者の図が豊富なブログで一度大雑把に理解し、もう一度公式のチュートリアルを読むことにしました。
gitの内部構造について知りたいのであればこれら2つを読めばかなり理解が捗るのではないかと思います。

これらを読んで理解した、私達の目的に直接携わる部分は以下の様にまとめられます。

  1. commit同士の関係はparentによって辿る片方向リスト
  2. branchについての情報は先頭のreferenceと上記の片方向リストでしかわからない
  3. 従って、localとremoteのcommitのどちらが進んでいるかを保存しているものはない

1つ目については、gitのソースのcommit.hを読めばcommitが構造体として定義されているため一応知ることが出来ます。しかし、全体像を把握するためには大量のソースを読む必要があるので、こういった「まとめ」を読むことが有効なのではないかと思います。
考え方が先、実装は後ですね。

全探索

上記のことが分かったので、commit同士の関係性を知る方法が定まりました。 すなわち、 remoteとlocalのbranchの先頭のreferenceからparentをたどっていき、一致した時の状態によって判断する というものです。

しかし、この手順を実装するのは若干面倒であることがわかりました。というのも、二つのcommitの関係はただの直線関係だけではないからです。

その関係のパターンについて、図を用いて説明します。
図中、左にあるノードのcommitがparentであるとします。

パターン1 直線上の場合

関係を探るためには、localとremoteそれぞれのheadからparentをたどって行き、どちらかのheadに到達すれば分かるので容易です。

以下の3パターンの内、localがremoteより進んでいるもののみがamend可能であるべきです。

f:id:SWIMATH2:20151103213443p:plain:w300 f:id:SWIMATH2:20151103213447p:plain:w300 f:id:SWIMATH2:20151103213452p:plain:w300

パターン2 分岐している場合

これは手順が面倒で、localとremoteそれぞれのheadからparentをたどっていき、お互いのheadに到達せずに一致する所があったら分岐している、というアルゴリズムが考えられます。

f:id:SWIMATH2:20151103213606p:plain:w300

この場合はremoteのheadのcommitがlocalのheadのcommitに依存していないので、amend出来るようにします。

これらに加えて、mergeしている場合などにparentが複数あるパターンがあるので、全探索の手順はかなり複雑なものになりそうです。
また、gitを使っていて生じうるパターンを数多く考えるのはかなり困難を極めていました。

git statusを手探る

全探索の実装を始める前に冷静になって考えなおしてみたところ、git statusをした際に表示される画面では、localのheadがaheadなのかbehindなのか表示されるため、この情報を取得することができれば良いのではないかという予測を立てることが出来ました。

ということで、git statusした際の動きを前回同様手探ってみたところ、remote.cの中にあるstat_tracking_info()という関数によってbranch同士の情報を得ていることがわかりました。
すなわち、commit --amendした際にこの関数を呼べば、push済みか否かのチェックが出来るということです。

結局、施した変更は以下のとおりです。

diff --git a/builtin/commit.c b/builtin/commit.c
index 63772d0..8a9a5b3 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -32,6 +32,7 @@
 #include "sequencer.h"
 #include "notes-utils.h"
 #include "mailmap.h"
+#include "remote.h"

 static const char * const builtin_commit_usage[] = {
        N_("git commit [<options>] [--] <pathspec>..."),
@@ -1125,6 +1126,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
                                      struct wt_status *s)
 {
        int f = 0;
+       int ours, theirs;
+       const char *full_base;
+       struct branch *branch = branch_get(NULL);

        argc = parse_options(argc, argv, prefix, options, usage, 0);
        finalize_deferred_config(s);
@@ -1149,6 +1153,12 @@ static int parse_and_validate_options(int argc, const char *argv[],
                else if (whence == FROM_CHERRY_PICK)
                        die(_("You are in the middle of a cherry-pick -- cannot amend."));
        }
+
+       stat_tracking_info(branch, &ours, &theirs, &full_base);
+       if (amend && ours == 0) {
+               die(_("You've already pushed this branch."));
+       }
+
        if (fixup_message && squash_message)
                die(_("Options --squash and --fixup cannot be used together"));
        if (use_message)

この関数を発見する事ができたことで、変更がかなり容易なものとなりました。 面倒な処理を実装するときには、「その実装が既に為されているのではないか?」と考え、似たような処理をしてそうな部分を探してみるのが有用な手段の一つなのではないかと思います。

git mailing listへの投稿と今後の課題

目的を達成できた私たちは、git本体にマージされることは出来ないかと考えgitのメーリングリストに投稿してみることにしました。

チームメンバーのtkk君が英語で投稿してくれたのですが、2時間後には他の開発者からコメントの返信が来たのでそのスピードに驚きました。
とはいってもコメントはかなり要求水準が高く、以下の様な修正点を挙げられました。

  1. remoteにブランチがあることを確認できていない
  2. 後方互換性が弱いので、オプションで指定できるようにするべき
  3. テストを書くべき

1については簡単に修正ができるのですが、オプションで指定するとなるとまた別の部分を手探る必要が生じ、さらにテストを書くとなると全く別のシェルスクリプトを書かねばならないため、実験のタイムリミットを考慮して修正せずに終えることにしました。

テストや後方互換性の重要性については理解しているつもりでしたが、実際に多くの人に使われるソフトウェアに携わると考えると、このように厳しくチェックされることでより再認識できたように感じます。

今回は時間の制約上諦めることになりましたが、今後の課題としてこれらの改善点を記しておくことにします。

謝辞

ご指導頂いた先生とTAの方々やチームメンバーの二人のおかげでどうにか開発が進められました。ありがとうございました。