7月のCocoa勉強会の発表「Three20のlintについて調べてみた」
書くのすっかり忘れてた。前々回、7月のCocoa勉強会の自分の発表です。
スライドはこちら。
前置き
Three20は多数の開発者が参加していることもあって、コードのスタイルを検証・訂正する"lint"機能を提供しています。Objective-C/Cocoaでその手のツールはめずらしく、どのように実装しているか興味があったので調べてみました。
きっかけ
Three20のMLをながめてたら「lintツールつくって、それで修正かけてみたよ」(メール"Three20 Lint Tool"、pull request)というのがありました。
で、その修正の数がまあすごいたくさんなんです。で、これはどうやってるのかなと興味もったわけです。
Three20のスタイルガイド
Three20にはコーディングスタイルガイドがあって
- Copyrightの表記
- importの順番
- 1行の長さ
- インデント/空白/改行
- init/deallocは先頭に
などなどが決められています。
これらが守られていないコードがあるとき、警告を出したり、訂正するのがlintの役割になります。コミットやpull requestする前にそれくらいは確認しとけってことですね。
試してみよう
となると、実際にどんな感じが試してみたいですよね。
- チェックアウトする
- てきとうに変更する
- Xcodeでビルドする
警告がでましたね。コンパイル時の警告と同じようにコードへジャンプもできます。
実現のしくみ
ざっとまとめると以下のようになっています。
- lintはpythonスクリプトで、ビルド時にスクリプトを実行している。
- lint機能の実装はふつうにテキスト処理(行に分割→チェック処理にかける)
- 決まった形式で出力すると、Xcodeはそれをコンパイラの警告として扱うことを利用
- プロジェクトのファイルの判定は.xcodeprojファイルのコメントを分析
clangのライブラリを使えるpythonのライブラリがあったように記憶していたので、そのへんを期待していたのですが、そんなことはありませんでした。
lintはpythonスクリプト
src/scripts/lintが実体になります。どう見てもpythonスクリプトですね。-dまたは--delintオプションで訂正を行ってくれるようです。
lintの実装
カナメのlint部分を見てみましょう。
if len(line) > maxlinelength: did_lint_cleanly = False nwarnings = nwarnings + 1 # This is not something we can fix with the delinter. if isdelinting: logger.error('I don\'t know how to split this line up.') else: logger.error('Line length > %d'% maxlinelength)
これは1行の長さですね。
if re.search(r'^@property\(', line): nwarnings = nwarnings + 1 if isdelinting: line = line.rstrip(' \t') nwarningsfixed = nwarningsfixed + 1 else: did_lint_cleanly = False logger.error('Must be a space after the @property declarator')
これは@propertyの後にスペースを空けろてことですね。
# Trailing whitespace if re.search('[ \t]+$', line): nwarnings = nwarnings + 1 if isdelinting: line = line.rstrip(' \t') nwarningsfixed = nwarningsfixed + 1 else: did_lint_cleanly = False logger.error('Trailing whitespace')
これは行末のスペースですね。
って、ぜんぶテキストの処理で構文解析とかぜんぜんしないのかー。あと、行単位の処理していて、複数行に渡る・・・なんかは今のところこのlintではサポートされていないようです。
Xcodeへの表示
決まった形式で文字列を出力すると、Xcodeがコンパイラの警告・エラーと同様に扱うってやつですね。ユニットテストを提供するテスティングフレームワークなんかでもよく使われている手法です。
プロジェクトのファイル
lintそのものはファイルごとに処理するものですから、対象のファイルを特定する必要があります。
あたりかなあと想像しつつ、見てみました。src/scripts/Pbxproj.pyのget_built_headersとget_built_sourcesがその実装になります。
って、コメントから抽出してるのかよ!!いや、確かにじゅうぶんに機能するんですけど、、、その発想はなかったっす。
感想
なんだかすごくフツーというか、ちょっと肩透かしな感じになってしまいました。
他にもObjective-Cで似たようなことをしているところはないかと探してみたのですが、google-toolbox for macやWebkitでもそういうものはありませんでした。これらのプロジェクトではコードレビューを重視していて、人間が確認するから、そもそもへんなパッチを書くヤツは少ないだろう、みたいな考えがあるのかなあと思いました。勝手な推測ですが。
Javaなんかはこの手のツールが発展している印象がありますが、Objective-Cは大規模とかエンタープライズとは縁遠いので、そういう開発スタイルのちがいもあるんでしょうね。