2016-08-29 5 views
3

コマンドラインツールに渡されるedn-configファイルの検証プロセスを説明する非常にネストされたclojureコードを考えてみましょう。cli-tool用のconfig-fileの検証プロセス:ネストされたifを回避する

(defn -main [& [config-path]] 
    (if config-path 
    (if-let [content (read-file config-path)] 
     (if-let [raw-data (read-edn content)] 
     (if-let [parsed (parse raw-data)] 
      (start-processing parsed) 
      (error "parsing-error")) 
     (error "invalid-edn-format")) 
     (error "file-not-found")) 
    (error "no argument")) 

注:呼び出される関数は、以下のネストされた&少ない不可欠な方法がそうするためにこの呼び出しダミー

です。ここで改善の提案はありますか?

上記の関数は、実際には、チェーン内で呼び出される4つの検証関数で煮詰めることができます:(1)引数チェック、(2)ファイル読み込み、(3)パーズ-EDN、データ。彼らは "エラー"を扱う方法が異なります:1と4については、私はclojure.specを使用しているので、clojure.spec/invalidが失敗で返されます。他の人(2人と3人)は、何かが間違っているときに例外をスローします。 これは、ここで抽象化することを特に困難にする。

彼らは検証に対処し、(この場合は例外で)エラー報告の一貫性のある手段を使用することができますので、私はロジックの部分ごとに別々の機能を書いて検討する

答えて

1

Eitherモナドを使用する。あなたがEitherを返すために、あなたの関数を変更する必要がまず

: - 起こったエラー

(require '[cats.monad.either :as either]) 

(defn assert-config-path-e [config-path] 
    (if config-path 
    (either/right config-path) 
    (either/left "no argument"))) 

(defn read-file-e [config-path] 
    ;; interpret return value as error 
    (if-let [content (read-file config-path)] 
    (either/right content) 
    (either/left "file-not-found"))) 

(defn read-edn-e [content] 
    (try 
    (read-edn content) 
    (catch ... 
     ;; interpret thrown exception as error 
     (either/left "invalid-edn-format")))) 

(defn parse-e [raw-data] 
    (if-let [parsed (parse raw-data)] 
    (either/right parsed) 
    (either/left "parsing-error"))) 

Right値は、成功した計算、Leftを表します。その後

do -notation/mletを使用して、すべてのものを組み合わせる:

(require '[cats.core :as cats]) 

(defn -main [& [config-path]] 
    (cats/mlet [_ (assert-config-path-e config-path) 
       content (read-file-e config-path) 
       raw-data (read-edn-e content) 
       parsed (parse-e raw-data)] 
    (cats/return (start-processing parsed))) 
+0

これはきれいなもののように見えます。2つの質問:あなたのコードには、エラー文字列で何をすべきか(例えば、それらを印刷すること)は何も言及されていません。同様に:なぜ猫ですか? clojure.algo.monadsでもできますか?努力をいただきありがとうございます!私たちはかなりしばらくの間、生産にそれを使用しているので、 –

+0

@AntonHarald私は、一例では猫を使用しました。私はエラー文字列に関してあなたの質問を得られませんでした。パイプライン中にエラーが発生した場合、通常はエラーオブジェクト(この実装ではエラーを記述する文字列)を含む 'left'値が返されます。この機能は 'algo.monads'を使って実装することができますが、' algo.monads'ライブラリには 'Either'モナドの実装がないので、あなた自身で実装する必要があります。 – OlegTheCat

0

(defn validate-config-path [config-path] 
    (if config-path 
    config-path 
    (throw ...))) 

(defn read-raw-config [config-path] 
    (try 
    (read-file config-path) 
    (catch ... 
     (throw ...)))) 

(defn read-edn-config [raw-config] 
    (try 
    ... 
    (catch ... 
     (throw ...)))) 

(defn read-parsed-config [edn-config] 
    (try 
    ... 
    (catch ... 
     (throw ...)))) 

(defn -main [& [config-path]] 
    (try 
    (-> config-path 
     validate-config-path 
     read-edn-config 
     read-parsed-config) 
    (catch Exception e 
     (error (.getMessage e))))) 

このアプローチでは、あなたが簡単にできます各部分を別々にテストし(別々の機能でカプセル化されているように)、別の場所で再利用してください。また、ユーザーにエラーを表示する方法を一元的に処理する方法を集中管理し、ユーザーにプログラム上のエラーをどのように表示するかを変更することもできます。

1

delayを使用すると、評価が延期されますが、ローカルのlet変数に値をバインドすることができます。 andを使用すると式が評価されますが、希望するとおりにnilで短絡します。 condは特定のエラーメッセージを表示し、エラーがなければ最終ステップを評価します。

(defn -main [& [config-path]] 
    (let [content (delay (read-file config-path)) 
     raw-data (delay (read-edn @content)) 
     parsed (delay (parse @raw-data))] 
    (and config-path 
     @content 
     @raw-data 
     @parsed) 
    (cond 
     (nil? config-path) (error "no argument") 
     (nil? @content) (error "file-not-found") 
     (nil? @raw-data) (error "invalid-edn-format") 
     (nil? @parsed)  (error "parsing-error") 
     :else (start-processing @parsed)))) 

それは、これらの利点があります。

  • それは理解しやすい
  • 平らだだけでは機能と呼ばれる
  • 葉を、物事を行うためにそれらを強制しない(あなたがdelay乗り越えるたら、多分?)それらの定義内ではない、関数の純度を維持する
  • マクロには関係しません(悪いことではありませんが、コア関数を使うのはプラスです)
関連する問題