I've been writing some code in Dart. I really love the factory constructor, but I'm afraid that I'm abusing it's usefulness. In particular, when I write a value object class, I sometimes return null if the validation fails.
class EmailAddress {
static final RegExp _regex = new RegExp(...);
final String _value;
factory EmailAddress(String input) {
return _regex.hasMatch(input) ? new EmailAddress._internal(input) : null;
}
const EmailAddress._internal(this._value);
toString() => _value;
}
At first, this doesn't seem all that bad. However, when you actually use it, this is what you see.
methodThatCreatesAnEmailAddress() {
var emailAddress = new EmailAddress("definitely not an email address");
...
}
The argument why this is bad is that a developer coming from another statically typed language, such as Java or C++, would expect emailAddress
to always be initialized to a non-null value. The argument why this is perfectly acceptable is that the constructor is factory and, as such, is allowed to return a null
value.
So is this bad practice or taking advantage of a useful feature?
Returning null
value as result from a factory is acceptable because the builtin factory
feature in Dart Factory software concept does not have any null-restriction.
On the other hand I can rephrase your question "Is it acceptable to return null from a equality operator"
bool operator ==(other) {
return null;
}
This is also acceptable because there is no such restriction that this operator cannot return the null
value.
But there is another question? Why do it and how to avoid it?
factory EmailAddress(String input) {
return _regex.hasMatch(input) ? new EmailAddress._internal(input) :
throw "something went wrong";
}
P.S.
My personal opinion that returning null
from factory
in Dart is a bad practice
because factories in Dart are very difficult to distinguish from contructors.
From the outside they looks like contructors with the difference that they are more powerful because can construct different kinds of objects.
And they also have their restrictions but this is another story...
Please don't do this. As a user of a constructor I expect to receive an instance of the constructor's class. It's ok to return a pre-existing instance or an instance of a subtype in Dart, but don't return null
.
I'd recommend one of two options to do what you want here:
throw an exception on invalid inputs. This way at least the error is early, rather than later if you've stored the null
somewhere.
Use a static method instead of a constructor. Static methods can return null
and not be as confusing.
Provide a fallback path, such as int.parse
does. You can accept a callback that will be called on an error.
I prefer 1 or 3 myself. I'd like to know explicitly when something isn't valid.
It's bad practice. When someone calls a constructor they expect a non-null value.
For your case I might do validation in a static method:
class EmailAddress {
final String _value;
static final RegExp _regex = new RegExp(r"...");
static bool isValid(String email) => _regex.hasMatch(email);
EmailAddress(this._value) {
if (!isValid(_value)) throw "Invalid email: $_value";
}
}
Now you get code reuse and nice semantics. For instance:
querySelector("#sendButton").disabled = !EmailAddress.isValid(userEmail);
I'm going to dissent from other answers: at least for named factory
constructors, I don't see anything wrong with returning null
.
The main differences between a factory
constructor and a static
method are that a factory
constructor can be used with new
and can be the unnamed, default constructor. Using new
is now discouraged, so a named factory
constructor won't be distinguishable from a static
method invocation at a callsite.
I don't see anything wrong with a static
method returning null
, and therefore I don't see anything wrong with a named factory
constructor returning null
either.
If the factory
constructor is unnamed, then I would agree that returning null
is likely to be unexpected by the caller and probably should be avoided.