When is it right for a constructor to throw an exception? (Or in the case of Objective C: when is it right for an init\'er to return nil?)
It seems to me that a cons
A constructor should throw an exception when it is unable to complete the construction of said object.
For example, if the constructor is supposed to allocate 1024 KB of ram, and it fails to do so, it should throw an exception, this way the caller of the constructor knows that the object is not ready to be used and there is an error somewhere that needs to be fixed.
Objects that are half-initialised and half-dead just cause problems and issues, as there really is no way for the caller to know. I'd rather have my constructor throw an error when things go wrong, than having to rely on the programming to run a call to the isOK() function which returns true or false.
I can't address best practice in Objective-C, but in C++ it's fine for a constructor to throw an exception. Especially as there's no other way to ensure that an exceptional condition encountered at construction is reported without resorting to invoking an isOK() method.
The function try block feature was designed specifically to support failures in constructor memberwise initialization (though it may be used for regular functions also). It's the only way to modify or enrich the exception information which will be thrown. But because of its original design purpose (use in constructors) it doesn't permit the exception to be swallowed by an empty catch() clause.
See C++ FAQ sections 17.2 and 17.4.
In general, I have found that code that is easier to port and maintain results if constructors are written so they do not fail, and code that can fail is placed in a separate method that returns an error code and leaves the object in an inert state.
The usual contract in OO is that object methods do actually function.
So as a corrolary, to never return a zombie object form a constructor/init.
A zombie is not functional and may be missing internal components. Just a null-pointer exception waiting to happen.
I first made zombies in Objective C, many years ago.
Like all rules of thumb , there is an "exception".
It is entirely possible that a specific interface may have a contract that says that there exists a method "initialize" that is allowed to thron an exception. That an object inplementing this interface may not respond correctly to any calls except property setters until initialize has been called. I used this for device drivers in an OO operating system during the boot process, and it was workable.
In general, you don't want zombie objects. In languages like Smalltalk with become things get a little fizzy-buzzy, but overuse of become is bad style too. Become lets an object change into another object in-situ, so there is no need for envelope-wrapper(Advanced C++) or the strategy pattern(GOF).
The constructor's job is to bring the object into a usable state. There are basically two schools of thought on this.
One group favors two-stage construction. The constructor merely brings the object into a sleeper state in which it refuses to do any work. There's an additional function that does the actual initialization.
I've never understood the reasoning behind this approach. I'm firmly in the group that supports one-stage construction, where the object is fully initialized and usable after construction.
One-stage constructors should throw if they fail to fully initialize the object. If the object cannot be initialized, it must not be allowed to exist, so the constructor must throw.
As far as I can tell, no-one is presenting a fairly obvious solution which embodies the best of both one-stage and two-stage construction.
note: This answer assumes C#, but the principles can be applied in most languages.
First, the benefits of both:
One-stage construction benefits us by preventing objects from existing in an invalid state, thus preventing all sorts of erroneous state management and all the bugs which come with it. However, it leaves some of us feeling weird because we don't want our constructors to throw exceptions, and sometimes that's what we need to do when initialization arguments are invalid.
public class Person
{
public string Name { get; }
public DateTime DateOfBirth { get; }
public Person(string name, DateTime dateOfBirth)
{
if (string.IsNullOrWhitespace(name))
{
throw new ArgumentException(nameof(name));
}
if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
{
throw new ArgumentOutOfRangeException(nameof(dateOfBirth));
}
this.Name = name;
this.DateOfBirth = dateOfBirth;
}
}
Two-stage construction benefits us by allowing our validation to be executed outside of the constructor, and therefore prevents the need for throwing exceptions within the constructor. However, it leaves us with "invalid" instances, which means there's state we have to track and manage for the instance, or we throw it away immediately after heap-allocation. It begs the question: Why are we performing a heap allocation, and thus memory collection, on an object we don't even end up using?
public class Person
{
public string Name { get; }
public DateTime DateOfBirth { get; }
public Person(string name, DateTime dateOfBirth)
{
this.Name = name;
this.DateOfBirth = dateOfBirth;
}
public void Validate()
{
if (string.IsNullOrWhitespace(Name))
{
throw new ArgumentException(nameof(Name));
}
if (DateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
{
throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
}
}
}
So how can we keep exceptions out of our constructors, and prevent ourselves from performing heap allocation on objects which will be immediately discarded? It's pretty basic: we make the constructor private and create instances via a static method designated to perform an instantiation, and therefore heap-allocation, only after validation.
public class Person
{
public string Name { get; }
public DateTime DateOfBirth { get; }
private Person(string name, DateTime dateOfBirth)
{
this.Name = name;
this.DateOfBirth = dateOfBirth;
}
public static Person Create(
string name,
DateTime dateOfBirth)
{
if (string.IsNullOrWhitespace(Name))
{
throw new ArgumentException(nameof(name));
}
if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
{
throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
}
return new Person(name, dateOfBirth);
}
}
Aside from the aforementioned validation and heap-allocation prevention benefits, the previous methodology provides us with another nifty advantage: async support. This comes in handy when dealing with multi-stage authentication, such as when you need to retrieve a bearer token before using your API. This way, you don't end up with an invalid "signed out" API client, and instead you can simply re-create the API client if you receive an authorization error while attempting to perform a request.
public class RestApiClient
{
public RestApiClient(HttpClient httpClient)
{
this.httpClient = new httpClient;
}
public async Task<RestApiClient> Create(string username, string password)
{
if (username == null)
{
throw new ArgumentNullException(nameof(username));
}
if (password == null)
{
throw new ArgumentNullException(nameof(password));
}
var basicAuthBytes = Encoding.ASCII.GetBytes($"{username}:{password}");
var basicAuthValue = Convert.ToBase64String(basicAuthBytes);
var authenticationHttpClient = new HttpClient
{
BaseUri = new Uri("https://auth.example.io"),
DefaultRequestHeaders = {
Authentication = new AuthenticationHeaderValue("Basic", basicAuthValue)
}
};
using (authenticationHttpClient)
{
var response = await httpClient.GetAsync("login");
var content = response.Content.ReadAsStringAsync();
var authToken = content;
var restApiHttpClient = new HttpClient
{
BaseUri = new Uri("https://api.example.io"), // notice this differs from the auth uri
DefaultRequestHeaders = {
Authentication = new AuthenticationHeaderValue("Bearer", authToken)
}
};
return new RestApiClient(restApiHttpClient);
}
}
}
The downsides of this method are few, in my experience.
Generally, using this methodology means that you can no longer use the class as a DTO because deserializing to an object without a public default constructor is hard, at best. However, if you were using the object as a DTO, you shouldn't really be validating the object itself, but rather invaliding the values on the object as you attempt to use them, since technically the values aren't "invalid" with regards to the DTO.
It also means that you'll end up creating factory methods or classes when you need to allow an IOC container to create the object, since otherwise the container won't know how to instantiate the object. However, in a lot of cases the factory methods end up being one of Create
methods themselves.