「TDD Boot Camp 札幌」にSqueak Smalltalkで参加


1日目は @mrknさんとペアプロで、2日目はソロでしたが、例題のレガシーコードを昼休みに Squeak Smalltalk に書き直してから、仕様化テスト書きとリファクタリングまでやってみました。両日ともとても充実していました。振り返りでも言いましたが、テストのある生活の安心感と、TDD の躍動感・ライブ感と Smalltalk との相性の良さをあらためて実感できました。


元になったレガシーコードの公開は避けて欲しいということでしたので、書き足したテストとリファクタリング後のコードだけ載せておきます。もっとも当日ご参加のみなさんに感じ取っていただけたであろう Smalltalk のパワーについては、この拙いコードだけからはまったく伝えられないので、それがちょっと残念です。^^;


風邪で不調をおしての来札、熱いご講演とご指導をいただいた輝きの @t_wadaさん、運営の @shuji_w6eさんはじめスタッフの皆様、ありがとうございました。今回は準備が間に合わなかったのですが、Smalltalkリファクタリングブラウザのデモとかできたらよかったですね。またの機会に。

Object subclass: #StrParser
   category: 'TDDBC-LegacyCode'


StrParser >> parse: string
   | separators subStrings tokens |
   
   separators := self class separators.

   separators do: [:sep |
      subStrings := string findBetweenSubStrs: {sep}.
      subStrings size >= 2 ifTrue: [
         tokens := {subStrings at: 1. sep. subStrings at: 2}.
         ^ tokens.
      ]
   ].

   ^ nil

StrParser class >> separators
   ^#('<=' '>=' '<' '>' '==' '+=' '-=' '!=' '=' '+' '-' )
TestCase subclass: #StrParserTest
   category: 'TDDBC-LegacyCode'

StrParserTest >> test01インスタンス化する
   | sp |
   sp := StrParser new.
   self assert: sp notNil

StrParserTest >> test02parseに空文字列を渡す
   "→nilを返す "
   | sp result |
   sp := StrParser new.
   result := sp parse: ''.
   self assert: result isNil

StrParserTest >> test03parseに1プラス1を渡す
   "→演算子の前後で区切って返す "
   | sp result |
   sp := StrParser new.
   result := sp parse: '1+1'.
   self assert: result = #('1' '+' '1')

StrParserTest >> test05parseメソッドに1イコールイコール2を渡す
   "→演算子の前後で区切って返す "
   | sp result |
   sp := StrParser new.
   result := sp parse: '1==2'.
   self assert: result = #('1' '==' '2')

StrParserTest >> test06parseメソッドに1空白イコール空白2を渡す
   "→演算子の前後で区切って返す。トークンの前後のスペースは放置。 "
   | sp result |
   sp := StrParser new.
   result := sp parse: '1 = 2'.
   self assert: result = #('1 ' '=' ' 2')

StrParserTest >> test07parseメソッドに1空白小なり空白2を渡す
   "→演算子の前後で区切って返す。 "
   | sp result |
   sp := StrParser new.
   result := sp parse: '1 < 2'.
   self assert: result = #('1 ' '<' ' 2')

StrParserTest >> test08parseメソッドに1空白小なり空白2空白小なり3を渡す
   "→2の直後の区切り文字列までをトークン対象としてトークンを返す。 "
   | sp result |
   sp := StrParser new.
   result := sp parse: '1 < 2 < 3'.
   self assert: result = #('1 ' '<' ' 2 ')

StrParserTest >> test09parseメソッドがトークン化できる区切り文字列群
   | separators sp result |
   separators := StrParser separators.
   sp := StrParser new.
   separators do: [:sep |
      result := sp parse: '1 ', sep, ' 2'.
      self assert: result = {'1 '. sep. ' 2'}]

StrParserTest >> test10parseメソッドに1小なりを渡す
   "トークンを2つ以上切り出せないためnilを返す"
   | sp result |
   sp := StrParser new.
   result := sp parse: '1 <'.
   self assert: result isNil

StrParserTest >> test11parseメソッドに1小なり小なり2を渡す
   "1<<2でも1<2と同じ振る舞いをする"
   | sp result |
   sp := StrParser new.
   result := sp parse: '1<<2'.
   self assert: result = #('1' '<' '2')

StrParserTest >> test11parseメソッドにabc小なりdefを渡す
   "アルファベット文字列を与えた場合でも演算子で区切ったトークン列を返す。"
   | sp result |
   sp := StrParser new.
   result := sp parse: 'abc<def'.
   self assert: result = #('abc' '<' 'def')

StrParserTest >> test01セパレーターの定義順に問題はないか
   "→あとで登場するセパレーターはそれまでのセパレーターの部分文字列ではいけない。"
   | separators |
   separators := StrParser separators.
   (1 to: separators size) do: [:idx |
      (separators first: idx - 1) do: [:sep |
         self deny: ((separators at: idx) includesSubString: sep)]]