top page > computer > haskell > coding > binary_analyzer > package > refactoring.html
更新日:
文責: 重城良国

バイナリ解析器: リファクタリング

動機

コードを書いていると、いろいろと汚ない部分が出てくる。パッケージ化する前に、全体的に見ながらきれいにしていきたい。バグが見つかることもある。

流れ

まずは-Wallによる警告をつぶしていく。そしてhlintによる警告を消す。その後はコードを一行ずつ検討していく。とくにコードを作成しているときにはimportリストを省略してしまうが、公開するコードではimportリストをきちんと明示するか、あるいはqualifiedな形でimportするべきだ。

型や値、関数の名前についても検討する必要がある。また、パッケージ名やモジュール名、モジュール構造についても考える必要がある。

-Wallとhlintによる警告をチェック

% ghci -Wall testAnalyzer.hs
(特に警告はない)
% hlint .
No suggestion

特に問題ないようだ。

ListLikeモジュールのリファクタリング

ListLike.hs

エクスポートリスト

module ListLike (ListLike(..), null, span) where

特に問題ない。ListLike(..)の部分ではListLikeのクラス関数がエクスポートされている。Element型族と以下の関数だ。

基本的にリストやByteStringの関数名を参考にしたので特に問題ないだろう。

インポートリスト

特に問題ない。

全体の構成について

初めにクラス宣言が来て、次にリスト、バイト列のインスタンス宣言が来ている。続いてnull関数とspan関数の定義。問題なさそうだ。

クラス宣言

Element型族やempty, cons, uncons関数に関しては問題ない。splitAt関数のデフォルト定義をすこし見ていくことにする。

	splitAt n xs | n <= 0 = (empty, xs) | otherwise =
		case uncons xs of
			Just (h, t) -> let
				(u, v) = splitAt (n - 1) t
				in (cons h u, v)
			_ -> (empty, empty)

Haskellではタブの使用は推奨されていないようだが、リーナス・トーバルズの書いたコーディング規約を読んでから僕は8文字タブ派だ。また、もちろん可読性も十分考慮したうえでということだが、できるだけ小さな長方形にコードが入るようにしたい。そこで、n <= 0のガードを関数名と同じ行にし、また横60文字に納まるようにinを(u, v) = ...の行と分けた。

インスタンス宣言

特に問題ない。

関数定義部分

span関数の形をすこし整えた。

span p s = case uncons s of
	Just (h, t) | p h ->
		let (u, v) = span p t in (cons h u, v)
	_ -> (empty, s)

条件をチェックする関数である引数を受ける変数は一文字の場合pとすることが多い。premize(前提)あたりから、か。sはsequenceのs。sをh(head)とt(tail)に分けて、そのtをさらにuとvに分けている。uとvを使ったのはアルファベット順でtuvと続くことから。

この時点でコード行数を数えてみる。空白行やエクスポートリストの部分は数に含めない。自分で作ったhssourceinfoパッケージを使う。

% hscodelines .
testAnalyzer.hs				68
ListLike.hs				35
Analyzer.hs				44
total					147

Analyzerモジュールのリファクタリング

Analyzer.hs

エクスポートリスト

module Analyzer (
	Analyzer, runAnalyzer, eof, spot, token,
	tokens, tokensWhile, listAll, listMap ) where

特に問題ないようだ。

インポートリスト

特に問題ないようだ。

全体の構成

Analyzer型を定義し、それをMonadのインスタンスにする。それを使ってFunctorやApplicativeのクラス関数を定義している。その後は関数定義が続く。

構成に問題はないようだ。

Analyzer型の定義

data Analyzer s a = Analyzer {
	runAnalyzer :: s -> Either String (a, s) }

(Analyzer s a)型の値は型sの並びからいくつか取り、そこから型aの値を取り出す関数。sはsequenceの意味。エラーが生じたら文字列で報告する。

インスタンス宣言

instance Monad (Analyzer s) where
	return = Analyzer . (Right .) . (,)
	a >>= b = Analyzer $ \s ->
		case runAnalyzer a s of
			Right (x, t) -> runAnalyzer (b x) t
			Left err -> Left err
	fail = Analyzer . const . Left

変数名等をいくつか変更した。

関数定義

eof :: LL.ListLike s => Analyzer s Bool

Analyzer aをAnalyzer sにした。以降他の関数でも同様にaをsに変えた。sはsequenceの意味。

eof = Analyzer $ Right . ((,) <$> LL.null <*> id)

(f <$> a <*> b)のようにすると、\x -> f (a x) (b x)をポイントフリースタイルで表現できる。bがidの場合は\x -> f (a x) xということになる。

spot p = Analyzer $ \s -> case LL.uncons s of
	Just (h, t) | p h -> Right (h, t)
	_ -> Left "Analyzer.spot: parse error"

エラーメッセージをどうするか。とりあえず[モジュール名].[関数名]は必要だと思う。メッセージはたとえばparse errorとかで良いのかもしれない。エラーの生じた位置については、今のところは、保存しないことにする。

loopWhile p m = do
	e <- p
	if e then return [] else
		(:) `liftM` m `ap` loopWhile p m

これは問題だ。関数名と動作が逆になっている。loopUntilに名前を変える必要がある。

loopUntil p m = do
	e <- p
	if e then return [] else
		(:) `liftM` m `ap` loopUntil p m
同様にmapWhileもmapUntilに改名する。
mapUntil _ _ [] = return []
mapUntil p m (x : xs) = do
	e <- p
	if e then return [] else
		(:) `liftM` m x `ap` mapUntil p m xs

エラーメッセージのモジュール名について

エラーメッセージのモジュール名はモジュール構成を変えると変化するので、moduleNameという変数に束縛しておいたほうが良いかもしれない。

以下のように定義しておく。

moduleName :: String
moduleName = "Analyzer"

以下のように使用する。

	_ -> Left $ moduleName ++ ".spot: parse error"

「パッケージ化」トップへもどる 「テストの見直し」へ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics