Is there a good way to check return types when refactoring?

Currently, when I refactor in Clojure, I use a template:

(defn my-func [arg1 arg2] (assert (= demo.core.Record1 (class arg1) "Incorrect class for arg1: " (class arg1)) (assert (= demo.core.Record1 (class arg2) "Incorrect class for arg2: " (class arg2)) ... 

That is, I find that I manually check return types in case the downstream part of the system changes them to something that I do not expect. (As in the case when I refactor and get a stack trace, I do not expect, then I express my assumptions as invariants and backtrack from there).

In a way, this is exactly the kind of invariant test that Bertrand Mayer expected. (The author of "Object-oriented software" , and a supporter of the idea of Design by contract ).

The problem is that I do not find them until runtime. I would be happy to find them at compile time simply by specifying what the function expects.

Now I know that Clojure is essentially a dynamic language. (While Clojure has a "compiler" of sorts, we should expect that applying values ​​to a function will only be implemented at runtime.)

I just want a good template to simplify refactoring. (i.e., to see all the effects of the transition from changing the argument to the function, not seeing it break into the first call, then move to the next, and then move to the next break.)

My question is: Is there a way to check return types when refactoring?

+5
source share
2 answers

You have a couple of options, only one of which “compiles” time:

Test

Because Clojure is a dynamic language, tests are absolutely necessary. This is your refactoring security system. Even in statically typed languages, tests are still used.

Preconditions and post-conditions

They allow you to check your invariants by adding metadata to your functions, for example, in this example from Michael Fogus’s blog :

 (defn constrained-fn [fx] {:pre [(pos? x)] :post [(= % (* 2 x))]} (fx)) (constrained-fn #(* 2 %) 2) ;=> 4 (constrained-fn #(float (* 2 %)) 2) ;=> 4.0 (constrained-fn #(* 3 %) 2) ;=> java.lang.Exception: Assert failed: (= % (* 2 x) 

core.typed

core.typed is the only option on this list that will give you compilation check time. Then your example would look like this:

 (ann my-func (Fn [Record1 Record1 -> ResultType])) (defn my-func [arg1 arg2] ...) 

This happens by running core.typed as a standalone action, perhaps as part of your test suite.

And still in the runtime validation / validation area there are even more options like bouncer and schema .

+2
source

If I understand you correctly, prisms / schemes should be your choice. https://github.com/plumatic/schema

 (s/defn ^:always-validate my-func :- SomeResultClass [arg1 :- demo.core.Record1 arg2 :- demo.core.Record1] ...) 

you should just turn off all checks before release, so this will not affect performance.

core.typed is fine, but as far as I remember, it makes you annotate all your code, while the circuit only allows you to comment on critical parts.

+3
source

Source: https://habr.com/ru/post/1241117/


All Articles