5 ms·
> I started out thinking the same thing (I moved from Java/Python to Scala). Ah, Scala. I could be prepared to believe that this is true for Scala (and similar
by winstonewert 10y ago
> I started out thinking the same thing (I moved from Java/Python to Scala).
Ah, Scala. I could be prepared to believe that this is true for Scala (and similarly advanced typed systems). It is when people claim they write fewer tests in java than in python that I found it really dubious.
> Yeah, happy to if you're interested. It's a useful exercise in thinking it through for myself as much as anything.
Ok here's another test:
These are action objects, they represent a sequence of user actions on a JobExtra.
const actions = [
JobExtra.actions.load(undefined),
JobExtra.actions.field('changeAmount', Money.actions.set(new Decimal("12.45"))),
JobExtra.actions.field('certifiedForemanRelated', Checkbox.actions.set(true)),
JobExtra.actions.field('appliedPercentage', Percentage.actions.set(new Decimal("4.5")))
]
I construct the final state by reducing across all those actions
const state = _.reduce(actions, (state, action) => JobExtra.reducer(state, action), undefined)
I verify that the job extra is considered valid afterwards.
strictEqual(true, isJobExtraValid(state))
To be considered valid:
1. The changeAmount must be set to non-zero
2. The certifiedFormanRelated must be false or the appliedPercentage must be true
But, if the user modifies the appliedPercentage, the certifiedFormanRelated should be automatically set to true.
So this, and a number of related tests verify that under different sequences of actions I end up the result I expected.
Is there a way to push this into the type system?
- lmm 10y agoYep. Ensuring something is valid is a great fit for use of types - you make sure that the type is inherently valid and it's impossible to construct in an invalid state. For 1., you could have a non-zero number type, or you could settle for a check in the JobExtra constructor. For 2., it sounds like this is a coproduct situation - a JobExtra is either certifiedFormanRelated or has an appliedPercentage. Scala doesn't quite have native support for coproducts the way it does for tuples, but you could represent it with a sealed class hierarchy: sealed trait ForemanOrPercentage case class ForemanRelated() extends ForemanOrPercentage case class AppliedPercentage(value: Decimal) extends ForemanOrPercentage or being a bit lazier you could just use an Option[Decimal] and say that if it's Some it's the applied percentage and if it's None then it's certified foreman related. In Java there isn't a "sealed" keyword, but you can emulate it with the visitor pattern - you'd define a ForemanOrPercentageVisitor and only ever operate on ForemanOrPercentages via the visitor. That way if someone creates their own subtype of ForemanOrPercentage it still has to behave like one or the other, because they still have to implement visit().
- winstonewert 10y agoI don't think that works for me. Since the user input modifies this object, the user can put the object into an invalid state. In fact, when the object is first created it is in an invalid state. So I can't simply define my types so that invalid state is impossible.
- lmm 10y agoIf your object has distinct possible states, maybe it should be two different objects. I don't think of user input as modifying my object (after all, this kind of object should be an immutable value) - rather I think of mapping my object to a set of form fields and mapping a set of form fields into a new object. (This goes well with having a component hierarchy model - just as my object is a value made of simpler values, its corresponding editor form is made of simpler editor forms). So I'd only create the (domain-level) object when the user's provided the right input to create the object. If you need a model of the user input in code, that's fine, but make that its own distinct type - then you have a single point at which to do validation, and the types make it very clear whether a given value is a business object (in which case it is necessarily valid) or unvalidated user input.
- winstonewert 10y agoI see now. That's very interesting, and shows how types can go much further then what we see in languages like Java. I'll have to try out one of these languages some day and see how well it works in practice.
- lmm 10y agoEven in Java it's possible (but it's not a great environment to learn these techniques in - they usually end up being very verbose when expressed in Java) .
- winstonewert 10y agoI don't know. The pure fact I can't be sure I won't have NullPtrExceptions in java code would make it pretty hard to accept not writing those tests.