商用ソフトウェア開発における「肥大化したコード」

製品開発では、チームに一貫したパターンを使用します。 これらは、よく知られた設計パターンであるだけでなく、たとえば、システム内のエラー処理パターン、システム間相互作用の要求および応答形式などです。 また、個別の開発では、ロジックと構造で繰り返されるすべての部分をメソッドでラップできるわけではありません。これは、コードの可読性と単純さにも寄与しません。



システムの数とサイズが増加し、パターンにわずかな変更が加えられると、コードを特定のパターンにするために、多くの場所をリファクタリングする必要があります。



jvmで代替言語を探索した後、clojureに決めました。 エラー処理パターンを実装する小さな例を次に示します。



Javaのエラー処理を次のようにフォーマットするとします。





{

... 仕事 ...

} catch InternalException e {

exceptionHelper。 addGroup e、TestTest。class。getName "...エラー..."

新しいペア "param1" 、param1 ;

new Pair "param2" 、param2 ;

eを投げ ます。

} catch 例外 e {

exceptionHelperをスローします。 generate ErrorRef。SYSTEM_ERROR、 「...エラー...」

新しいペア "param1" 、param1 ;

新しいペア "param2" 、param2 ;

new Pair "exception" 、MySerialization。exceptionToString e ;

}

}




clojureでは、 handler-ieマクロが対応するライブラリに実装され、このパターンのロジックを実装しています。 たとえば、 中央の関数では、次のマクロを使用してbottom関数の呼び出しを処理します。



defn middle [ a b ]

すなわち/ハンドラーすなわちすなわち/システムテスト"中間レベルの処理" nil [ a b ]

let [ result bottom a b ]

結果





コンパイル時に関数本体のコードがどのように展開されるかを見てみましょう。



macroexpand- 1 ' すなわち/ handler-ieすなわち/ system-test "中間レベルの処理" nil [ a b ]

let [ result bottom a b ]

結果




結果(読みやすくするために自動変数と完全な名前空間のサフィックスが削除されました):



let [ params reverse zipmap map str ' [ a b ]

[ a b ] ))

エラー場所現在の関数 ]

try let [ result bottom a b ]

結果

エラーをキャッチし ます。エンティティ。internalexception e

add-group-to-ie eエラープレース"中間レベル処理" params

eを投げる

java。lang。exception eをキャッチ

let [ error-ref error-map 。getname 。getclass e ))

error-ref if nil ?error-ref

make-system-error-ref system-test errorref-system- error

error-ref

すなわち make-ie error-ref error-place "中間レベル処理" e params ]

スロー )))




結果のコードは、Javaバージョンのパラメーター化された類似物であることがわかります。 clojureバージョンのもう1つの利点は、エラー処理ロジックの実装を変更するときに、この処理が使用されるプロジェクトのソースコードを変更する必要がないことです。 適切なプロジェクトを再構築するだけで十分です。



現時点では、マクロを使用してエラーを処理し、システム間応答を生成し、eDSL内でデータベースを操作します。 T.O. 私たちのプロジェクトで繰り返されるパターンはすべて、言語の単なる拡張になりました。



Lispに切り替えた後、ソースコードのサイズは桁違いに小さくなりました。 これは、マクロが原因であるだけでなく、便利な汎用clojureデータ構造、高次関数、およびクロージャーを使用して関数型スタイルで記述できるためでもあります。 同時に、clojureのライブラリを操作する機能、javaおよびj2eeフレームワークは失われませんでした。 そして最も重要なことは、現在、コードをすばやく作成する唯一の理由はプログラマ自身にあるということです。



ライブラリ操作例





ソースコード:



defn bottom [ a b ]

すなわち/ハンドラーすなわちすなわち/システムテスト「低レベル操作」

{ "java.lang.ArithmeticException" TestSystemEM $ ErrorRef / BAD_ARGS }

[ a b ]

let [結果 / a b ]

結果



defn middle [ a b ]

すなわち/ハンドラーすなわちすなわち/システムテスト"中間レベルの処理" nil [ a b ]

let [ result bottom a b ]

結果



defnテスター[ a b ]

let [ c + a b ]

すなわち/ハンドラーすなわちすなわち/システムテスト"パブリックインターフェイスアクション" nil [ a b c ]

let [ result middle a b ]

結果))





例外なく結果:



ユーザー> try テスター1 2

InternalException eをキャッチ

println すなわち/人間が読むことができる-すなわちe



1/2





例外あり:



ユーザー> try テスター1 0

InternalException eをキャッチ

println すなわち/人間が読むことができる-すなわちe



ErrorRef:BAD_ARGS

グループパラメータ:

className:libs.error.example $テスター

メッセージ:パブリックインターフェイスアクション

パラメータ:

名前:a、値:<int> 1 </ int>

名前:b、値:<int> 0 </ int>

名前:c、値:<int> 1 </ int>

グループパラメータ:

className:libs.error.example $ middle

メッセージ:中間レベルの処理

パラメータ:

名前:a、値:<int> 1 </ int>

名前:b、値:<int> 0 </ int>

グループパラメータ:

className:libs.error.example $ bottom

メッセージ:低レベル操作

パラメータ:

名前:b、値:<int> 0 </ int>

名前:a、値:<int> 1 </ int>

名前:例外、値:<java.lang.String>エラーメッセージ:ゼロ除算

java.lang.ArithmeticException:ゼロ除算

clojure.lang.Numbers.divide(Numbers.java:138)で

libs.error.exampleで$ bottom.invoke(example.clj:12)

libs.error.example $ middle.invoke(example.clj:17)

libs.error.exampleで$ tester.invoke(example.clj:23)

...

</java.lang.String>




All Articles