株式会社クリアコード > ククログ

ククログ


Emacs上でカラフルにdiffを表示する

ソフトウェア開発は小さな変更の積み重ねです。ソフトウェア開発ではバージョン管理システムを使うことが当たり前ですが、バージョン管理システムはその変更の積み重ねを記録しています。変更はdiffと呼ばれ、ソフトウェア開発をしていれば頻繁に目にします。例えば、コミット前に変更を最終確認するためにdiffを見ます。また、誰かがコミットすればdiff入りのメールで変更内容が関係者に通知されます。

さて、そんなよく目にするdiffをEmacs上でより見やすく表示する方法を紹介します。

そもそも、どうしてEmacs上でdiffを見るかというと、Emacsにはdiffを表示するための支援機能がたくさんあるからです。編集中のファイルのdiffを見たいならVCが提供するvc-diffC-x v =)を使います。バージョン管理システムで管理している複数のファイルのdiffを見たいならMagit(git用)やpsvn(Subversion用)などそれぞれのバージョン管理システムと連携する拡張パッケージの機能を使います。これらの機能を使うとコードを書きながらすぐに変更点を確認できるため、開発のリズムを崩さずにすみます。

行をカラフルにする

diffが簡単に見られる便利なEmacsですが、初期状態のdiff表示はあまり見やすくありません。

デフォルトのdiff表示

一番左に「-」がある行が削除された行で「+」がある行が追加された行ですが、パッと見てわかりません。

ということで、色をつけてみましょう。

;; 追加された行は緑で表示
(set-face-attribute 'diff-added nil
                    :foreground "white" :background "dark green")
;; 削除された行は赤で表示
(set-face-attribute 'diff-removed nil
                    :foreground "white" :background "dark red")

追加された行は緑っぽく、削除された行は赤っぽく表示します。

カラフルなdiff表示

色がついたのでパッと見て注目する場所がわかりますね。

ここでは、文字の色と背景色を両方指定しているので、フレームの色を白っぽくしていても黒っぽくしていてもどちらでも見やすく表示されます。自分でフェイスを変更して色を変える場合は、このように文字の色と背景色を両方指定するか、((class color) (background dark))などを使って白っぽいときと黒っぽいとき両方の設定をすることをオススメします。なお、文字の色と背景色を指定する方が楽です。

文字をカラフルにする

行に色を付けることでどの行が変更されたかをパッと見つけることができるようになりました。しかし、行の中のどの部分かはまだパッと見つけることはできません。行の中のどの部分が変更されたかを見つける方法はいくつかあります。例えば、gitには--word-diffというオプションがありますし、DocDiffというツールもあります。test-unit 2がやっている方法もあります。Cutterもtest-unit 2と同じ方法を使っています。

実はEmacsにも同じような機能があるのですが、あまり知られていないようです。おそらく、デフォルトのフェイスの設定が地味だからでしょう。以下のように設定してみてください。

;; 文字単位での変更箇所は色を反転して強調
(set-face-attribute 'diff-refine-change nil
                    :foreground nil :background nil
                    :weight 'bold :inverse-video t))

;; diffを表示したらすぐに文字単位での強調表示も行う
(defun diff-mode-refine-automatically ()
  (diff-auto-refine-mode t))
(add-hook 'diff-mode-hook 'diff-mode-refine-automatically)

文字単位の変更箇所は色を反転するようにして強調しています。

文字単位までカラフルなdiff表示

ぐっとわかりやすくなりました。nil"white"に変更していますね。

これらの機能はEmacsに標準でついているdiff-modeの機能です。

Magitのdiffもカラフルにする

Magitもdiff-modeを使っているのでこれまでの設定でdiffをカラフルに見やすくすることができます。しかし、ちょっとうまくないところがあるので、もうひと手間かけてみましょう。やることはここで設定したdiff-modeの表示をMagitでもできるようにすることだけです。

;; diffを表示しているときに文字単位での変更箇所も強調表示する
;; 'allではなくtにすると現在選択中のhunkのみ強調表示する
(setq magit-diff-refine-hunk 'all)
;; diffの表示設定が上書きされてしまうのでハイライトを無効にする
(set-face-attribute 'magit-item-highlight nil :inherit nil)

この設定がないと文字単位の違いを強調するためにひと手間増えたり、せっかくのカラフルな表示が見えなくなったりしまいます。この設定さえしておけば、Magitでもdiffを文字単位まで見やすく表示できます。

なお、psvnはこのような追加の設定をしなくてもdiff-mode用の設定が反映されます。

まとめ

Emacsでdiffをカラフルに見やすく表示する設定を紹介しました。まとめると以下のようになります。

;; diffの表示方法を変更
(defun diff-mode-setup-faces ()
  ;; 追加された行は緑で表示
  (set-face-attribute 'diff-added nil
                      :foreground "white" :background "dark green")
  ;; 削除された行は赤で表示
  (set-face-attribute 'diff-removed nil
                      :foreground "white" :background "dark red")
  ;; 文字単位での変更箇所は色を反転して強調
  (set-face-attribute 'diff-refine-change nil
                      :foreground nil :background nil
                      :weight 'bold :inverse-video t))
(add-hook 'diff-mode-hook 'diff-mode-setup-faces)

;; diffを表示したらすぐに文字単位での強調表示も行う
(defun diff-mode-refine-automatically ()
  (diff-auto-refine-mode t))
(add-hook 'diff-mode-hook 'diff-mode-refine-automatically)

;; diff関連の設定
(defun magit-setup-diff ()
  ;; diffを表示しているときに文字単位での変更箇所も強調表示する
  ;; 'allではなくtにすると現在選択中のhunkのみ強調表示する
  (setq magit-diff-refine-hunk 'all)
  ;; diff用のfaceを設定する
  (diff-mode-setup-faces)
  ;; diffの表示設定が上書きされてしまうのでハイライトを無効にする
  (set-face-attribute 'magit-item-highlight nil :inherit nil))
(add-hook 'magit-mode-hook 'magit-setup-diff)

その他のEmacsのオススメ設定が気になる人やEmacsを使いたくなったけどよい入門書はないかしら、という人はEmacs実践入門 - おすすめEmacs設定2012をどうぞ。

diffが見やすくなるとソースコードの変更を確認しやくなりますね。これまで以上に他の人のコミットも確認して、よくないコードを見つけたらどんどんよいコードにしていってください。

2012-04-03

中小企業倒産防止共済制度

今回は、クリアコードで利用している中小企業倒産防止共済という制度について紹介します。中小企業倒産防止共済は、独立行政法人中小企業基盤整備機構が提供している制度です。

機構のサイトでは、この制度について次のように紹介しています。

「貴方の会社が健全経営でも「取引先の倒産」という事態はいつ起こるかわかりません。経営セーフティ共済(正式名称:中小企業倒産防止共済制度)は、そのような不測の事態に直面された中小企業の皆様に迅速に資金をお貸しする共済制度です」

以下、簡単な制度の概要です。

  • 制度に加入できるのは中小企業。
  • 加入者は毎月一定の掛金(5千円から20万円)を積み立てます。また掛金の前納が可能です。
  • 取引先が倒産し、売掛金が回収不能となった場合、最大で掛金総額の10倍の融資が受けられます。
  • 掛金納付月数が40ヶ月以上で解約した場合、掛金総額のすべてが解約一時金として支払われます。

クリアコードにおいて、倒産防止共済制度は2つの役割を果たしています。 ひとつは取引先が倒産し、売掛金の回収が困難となったときに、資金の融資を受け、連鎖倒産を回避する役割です。 もうひとつは掛金を積み立てることによる節税です。

では、それぞれの役割について説明します。

取引先倒産時に備えて

取引先が倒産し、代金が回収できなくなると、代金の回収を見込んで立てられた資金繰り計画を見直す必要がでてきます。回収不能となった代金が多額であれば資金繰り計画への影響も大きく、借入などの手段によって資金を調達しなければなりません。

しかし、回収不能となった代金は損益計算書上では損失となり、赤字を計上することになります。赤字額が大きければ財務内容が悪化し、金融機関から融資を受けるのは難しくなるでしょう。資金調達の手段が断たれれば、場合によっては連鎖倒産という事態に陥ります。

このような事態を回避するための手段が中小企業倒産防止共済制度の共済金の貸付です。制度に加入して6ヶ月以上が経過していれば、回収不能となった代金と掛金総額の10倍のいずれか低い方の金額を上限として融資を受けることができます。例えば掛金総額が80万円の時に、1000万円が回収不能となった場合、800万円の融資が受けられます。連鎖倒産を防ぐという目的において、非常に有効な制度です。

節税対策として

中小企業倒産防止共済制度の掛金は、税法上の損金に計上することができます。しかし解約した場合は、解約手当金として掛金(一定の条件を満たせば掛金総額が満額)が戻ってきます。

また、掛金は前払が可能で、1年以内の前払分はその期の損金として認められます。そのため、毎月の掛金を20万円とした場合、最大で20万円*11ヶ月+240万円=460万円を損金計上することができます。

将来に備えて利益準備金を蓄えていくことは大切です。しかしそのためには一定の利益を計上し、法人税を納めたうえで、利益準備金を増やしていかなければなりません。効率的に利益を蓄え、いざというときに備えるということであれば、中小企業倒産防止共済制度もひとつの魅力的な選択肢となるのではないでしょうか。

※倒産防止共済掛金を保険積立金として資産計上することによって、利益を確保した上で、節税する方法もあるようです。経理処理については税理士事務所等にご確認ください。

タグ: 会社
2012-04-12

適切なサイズのコミットにする方法の案

以前、読みやすいコミットにする方法としてコミットメッセージの書き方小さくまとまったコミットの具体例を紹介しました。今回は、小さくまとまったコミットにするためのより一般的な方法のを紹介します。案としているのは、広く使えそうな気がしますが、思いついたばかりでまだ実例がないためです。この方法が使えそうな気がしたらぜひ試してみてください。

どのくらいのコミットが読みやすい適切なサイズかというのはケースバイケースのことがほとんどです。以前紹介した具体例が使えることは日に何回かあるでしょうが、1日に二桁くらいはコミットするはず*1なので、コミットの大半はケースバイケースで判断していることになります。では、ケースバイケースで判断しているときはどのように判断しているのでしょうか。感覚的にはケースバイケースで判断しているときも何かしら一般的な判断基準を使っていそうです。

そこで思いついた(気付いた)のが今回紹介する方法です。

やろうとしたことがパターンに当てはまったらそこでコミット

これまでの考えは、今やっている作業が小さくまとまったコミットのパターン(具体例)に当てはまるかどうかでコミットのサイズを判断すればよい、というものでした。この考えではパターンに当てはまらないケースに使えません。さらに、一般的に使えそうなパターンを増やすことは大変です。

そこで少し見方を変えてみます。パターンに当てはまらない変更をしているとします。このときはこれまでの考え方を使えません。この作業の途中でパターンに当てはまる変更をしたくなったとします。例えばtypoを見つけてしまったという状況です。そうなったら今の作業を早くキリのよい状態までもっていってコミットします。この状態から追加の改良などには手をだしません。エラーチェックが甘いまま残っているなどするかもしれませんが、それらは後の作業にまわして、今の状態から一番近いキリのよいところに着地します。その後にtypoを修正するコミットや後に回した作業をコミットします。

場合によっては着地点が遠すぎることもあるでしょう。そのときは、今の変更は一度横において*2パターンに当てはまる変更を修正して、また作業に戻るとよいでしょう。

このように、小さくまとまったコミットのパターンに当てはまったときに小さくコミットするという考え方だけではなく、パターンに当てはまったら今の作業を一段落させてコミットするという考え方も取り入れると、ケースバイケースで判断することが減るのではないでしょうか。もちろん、パターンに当てはまらなかった場合はケースバイケースで読みやすいコミットになるかを考える必要はあります。

まとめ

小さくまとまったコミットを見つけやすくする方法の案を紹介しました。これまでは、小さくまとまったコミットのパターンに当てはまったらその単位でコミット、という基準だけを使う方法でした。今回紹介した方法は、それに加えて、今の作業を切り上げる基準にもパターンを使うという方法です。この方法も使うことにより、パターンを使える機会が増えて、読みやすいコミットが増えることを期待しています。

*1 1時間に少なくとも1コミットはするだろうから、1日8時間開発していたら少なくとも8コミットくらいはするはず。もし、1時間に1コミットしかできなかったり1コミットもできなかったら、何か悩んだりつまづいたりしている状態だろうから、普段の状態なら10コミットはいくはず。

*2 Gitならgit stash

2012-04-17

テストをすっきり書く方法

はじめに

ソフトウェアを作るときには同時にテストも作ります。 テストを動かすことで、ソフトウェアが設計の通り動作しているかを確認できます。もし設計の通りに動作しない場合はテストが失敗し、ソフトウェアに期待する動作と現在の間違った動作が明確になります。 テストをすっきりと書くことができると、テストを読みやすくなり、また、きれいなソースコードのままで新しくテストを追加することができます。 今回は、そのすっきりとテストを書くための方法について説明します。

テストを追加していくと発生する問題

例えば、1つのテストケースの中にいろいろな機能のテストがある場合を考えます。 ここで、ある機能の実装を修正したので、この機能に関するテストを追加しようとしました。 テスト名に「テストのコンテキスト」と「テスト対象」を含めてどのような内容のテストかを示します。 このとき、ある機能に対して様々な動作をテストすることも多いため、すでにテストケースの中に追加したいテストとコンテキストを共有しているテストがある場合もあります。 ここで問題が発生します。すべてのテスト名にコンテキストを含めるとテスト名が長くなってしまいます。 さらに、これを色んなコンテキストで繰り返すことで、同じコンテキスト名を含んだテスト名が複数あると、同じ情報が何度も現れて見にくくなります。

テストのグループ分け

この問題を解決する方法がテストをクラスでグループ分けする方法です。 グループ分けすることで、ごちゃごちゃしたテストをすっきりと整頓できます。 つまり、テスト名を短く、またすっきりとした状態を保ったままテストを追加できるようになります。

実際にクラスによるグループ分けをどうやるかについて、全文検索エンジンgroongaのRubyバインディングであるrroongaのテストを例に挙げて説明します。 rroongaはgroongaをRubyから便利に使うためのライブラリで、内部でgroongaを使用しています。 ここでは1つのデータの集まりを表すレコードオブジェクトに関するテストを例にします。

以下はレコードに関するテストの一部です。 テスティングフレームワークはtest-unitを使っています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class RecordTest < Test::Unit::TestCase
  include GroongaTestUtils

  def test_have_column_id
    groonga = @bookmarks.add
    assert_true(groonga.have_column?(:_id))
  end

  def test_have_column_key_hash
    mori = @users.add("mori")
    assert_true(mori.have_column?(:_key))
  end

  def test_have_column_key_array_with_value_type
    groonga = @bookmarks.add
    assert_true(groonga.have_column?(:_key))
  end

  def test_have_column_key_array_without_value_type
    groonga_ml = @addresses.add
    assert_false(groonga_ml.have_column?(:_key))
  end

  def test_attributes
    values = {
      "uri" => "http://groonga.org/",
      "rate" => 5,
      "comment" => "Grate!"
    }
    groonga = @bookmarks.add(values)
    assert_equal(values.merge("_id" => groonga.id,
                              "content" => nil,
                              "user" => nil),
                 groonga.attributes)
  end

  def test_recursive_attributes
    @bookmarks.define_column("next", @bookmarks)

    top_page_record = @bookmarks.add(top_page)
    doc_page_record = @bookmarks.add(doc_page)

    top_page_record["next"] = doc_page_record
    doc_page_record["next"] = top_page_record

    expected = {
      "_id" => 1,
      "user" => nil,
      "uri" => "http://groonga.org/",
      "rate" => 5,
      "next" => {
        "_id" => 2,
        "user" => nil,
        "uri" => "http://groonga.org/document.html",
        "rate" => 8,
        "content" => nil,
        "comment" => "Informative"
      },
      "content" => nil,
      "comment" => "Great!"
    }
    expected["next"]["next"] = expected

    assert_equal(expected, top_page_record.attributes)
  end

  def test_key
    documents = Groonga::PatriciaTrie.create(:name => "Documents",
                                             :key_type => "ShortText")
    reference = documents.add("reference")
    assert_equal("reference", reference.key)
  end

  def test_value
    bookmark = @bookmarks.add
    assert_equal(0, bookmark.value)
    bookmark.value = 100
    assert_equal(100, bookmark.value)
  end
end

説明のために、上のソースコードからテストケース名とテスト名だけ抜き出して並べます。

1
2
3
4
5
6
7
8
9
10
class RecordTest
  def test_have_column_id; end
  def test_have_column_key_hash; end
  def test_have_column_key_array_with_value_type; end
  def test_have_column_key_array_without_value_type; end
  def test_attributes; end
  def test_recursive_attributes; end
  def test_key; end
  def test_value; end
end

テスト名を見ると、「どんなカラムを持っているか」という「コンテキスト(have_column)」のテストが4つ、「属性はどんな状態か」という「コンテキスト(attributes)」のテストが2つあることがわかります。 しかし、「どんなカラムを持っているか」という「コンテキスト(have_column)」をもつテスト4つ全てに、コンテキスト(have_column)を示す名前が入っています。 「属性はどんな状態か」という「コンテキスト(attributes)」をもつテストにも同じようにコンテキスト(attributes)を示す名前が入っているため、同じ情報が何度も現れて見にくくなる問題が発生しています。

そこで、「どんなカラムを持っているか」という「コンテキスト(have_column)」と、「属性はどんな状態か」という「コンテキスト(attributes)」でテストをグループ分けします。 まず、「have_column」をコンテキストをグループ分けしましょう。 対象となるのは以下のテストです。

1
2
3
4
5
6
class RecordTest
  def test_have_column_id; end
  def test_have_column_key_hash; end
  def test_have_column_key_array_with_value_type; end
  def test_have_column_key_array_without_value_type; end
end

まず、これらのテストをコンテキスト名を含んだクラスに移動します。

1
2
3
4
5
6
7
8
class RecordTest
  class HaveColumnTest
    def test_have_column_id; end
    def test_have_column_key_hash; end
    def test_have_column_key_array_with_value_type; end
    def test_have_column_key_array_without_value_type; end
  end
end

コンテキスト名を含んだクラスは移動前のクラスを継承します。 継承することで、自然と移動前のコンテキストを引き継いだテストを書くことができるからです。

1
2
3
4
5
6
7
8
class RecordTest
  class HaveColumnTest < self
    def test_have_column_id; end
    def test_have_column_key_hash; end
    def test_have_column_key_array_with_value_type; end
    def test_have_column_key_array_without_value_type; end
  end
end

selfはクラスの定義中だとそのクラスとなるため、ここでのselfはRecordTestになります。 最後に、テスト名からコンテキストを除きます。 すでにクラス名にコンテキストが含まれているため、コードでコンテキストを表現できているからです。

1
2
3
4
5
6
7
8
class RecordTest
  class HaveColumnTest < self
    def test_id; end
    def test_key_hash; end
    def test_key_array_with_value_type; end
    def test_key_array_without_value_type; end
  end
end

このように分類することで、コンテキスト(have_column)を表すのがクラス名だけになり、同じ情報が何度も現れる問題を解決することができました。

同じようにattributesのコンテキストについてもやってみましょう。 対象となるのは以下のテストです。

1
2
3
4
class RecordTest
  def test_attributes; end
  def test_recursive_attributes; end
end

まず、これらのテストをコンテキスト名を含んだクラスに移動します。

1
2
3
4
5
6
class RecordTest
  class AttributesTest
    def test_attributes; end
    def test_recursive_attributes; end
  end
end

コンテキスト名を含んだクラスは移動前のクラスを継承します。

1
2
3
4
5
6
class RecordTest
  class AttributesTest < self
    def test_attributes; end
    def test_recursive_attributes; end
  end
end

最後に、テスト名からコンテキストを除きます。

1
2
3
4
5
6
class RecordTest
  class AttributesTest < self
    def test_single; end
    def test_recursive; end
  end
end

test_attributesは、attributesというコンテキストでは単一の属性についてテストしているので、ここでは新しくtest_singleとしました。 コンテキストがattributesのテストに関しても、attributesというコンテキストが何度も現れる問題が解決されています。 グループ分けをした後のRecordテストは次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RecordTest
  class HaveColumnTest < self
    def test_id; end
    def test_key_hash; end
    def test_key_array_with_value_type; end
    def test_key_array_without_value_type; end
  end

  class AttributesTest < self
    def test_single; end
    def test_recursive; end
  end

  def test_key; end
  def test_value; end
end

コンテキストをクラス名に移動することで、関連するテストをまとめることができました。これにより、テストコード全体がすっきりしました。 今後、テストを追加するときも同じコンテキスト名を重複して書かなくてもよくなるため、すっきりした状態を保つことができます。

まとめ

テストをすっきりさせる方法としてテストをグループ分けする方法を紹介しました。 すっきりした状態ではテストも読みやすくなります。 また、きれいなソースコードのままテストを追加することもできます。

おまけ

今回の例に挙げたテストは、RSpecではdescribeを使って書くことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
describe Groonga::Record do
  describe "#have_column?" do
    it "should true for _id" do; end
    it "should true for _key of hash" do; end
    it "should true for _key of array that has value type" do; end
    it "should false for _key of array that doesn't have value type" do; end
  end

  describe "#attributes" do
    it "should return hash for record that doesn't have reference" do; end
    it "should return hash for record that has recursive reference" do; end
  end

  it "should get key" do; end
  it "should get and set value" do; end
end
タグ: Ruby | テスト
2012-04-25

«前月 最新記事 翌月»
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|