Interview question: Dependency Injection
Intro
This is part of a series that I plan to build on as time goes on: technical interview questions, dissected and laid bare for both interviewers and interviewees. You can also check out the previous one: Interview question: all items in table A but not in B.
This question is a little bit more complex and abstract at the same time. The post is written more for interviewers this time, because as candidates go, you need to read the links in it if you didn't know the concepts in it. This also is not a question with a single correct answer. It comes after asking about Dependency Injection as a whole and the candidate answering correctly.
I expect senior developers to be able to go through this successfully, it is not a test for junior developers, although depending on their previous experience juniors might be able to go through it and seniors be force to reason through it.
The test
Bonus introduction question: why use DI at all? Expected answers would be separation of concerns and testability.
The question has two steps.
Step 1: given the following code in a legacy application, improve it to use Dependency Injection:
public SomeClass {
public List<Item> GetItems(int days, string filter) {
var service = new ItemService();
return service.GetItems()
.Where(i => i.Time >= DateTime.Now.AddDays(-days));
}
}
Bonus questions:
- has the candidate worked with LINQ before?
- what does the code do?
Now, this question is about programming knowledge as it is for attention. There are three irregularities that can attract that attention:
- the most obvious: the service is being instantiated by calling the constructor
- the interviewer expects at the very least for the candidate to notice it and suggest moving the instantiation of the service in the constructor of the SomeClass class and inject it instead of using new
- there is the possibility of passing the service as a parameter, as well, but suggest that the signature of the method should remain the same to get around it. Anyway, one can discuss the idea of moving all dependencies to the constructor and/or the calling methods and get insight in the way the candidate is thinking.
- the unexplained string filter in the signature of the method
- the interviewer can either tell the candidate that it will become relevant later, because it will, or that this is a method that implements an interface, to which a more snarky candidate might reply that SomeClass implements nothing (bonus for attention)
- the use of DateTime.Now
- it is a static property that gives a different output every time so it should be taken into account for Dependency Injection or at least for unit testing
By now you have filtered out the majority of failing candidates and you are left with someone who used or at least understands DI, can read and understand code, has used or at least understood basic LINQ and you have also gauged their level of attention to detail.
If the candidate only talked about the service and they decided to create an interface for ItemService and then add it as a parameter for the constructor of SomeClass, ask them to write a unit test for the method, explain to them that testability is one of the goals of DI if you didn't cover this already
- bonus: see if they do unit testing or at least understand the concept
- if they do attempt to write the unit test, ask them what would happen if you would run the test in different days
The expected result of this part is that the candidate understands the need of abstracting DateTime.Now. It is interesting to note how they intend to abstract it, since they do not have access to the code and it is a static method/property to abstract.
Whether the candidate figured it out by themselves or it was explained to them, the expected answer is that DateTime.Now is abstracted by creating an IDateTimeService interface that is implemented as a wrapper over DateTime.
At this point the code should look like this:
public SomeClass {
private IItemService _itemService;
private IDateTimeService _dateTimeService;
public SomeClass(IItemService itemService, IDateTimeService dateTimeService) {
_itemService = itemService;
_dateTimeService = dateTimeService;
}
public List<Item> GetItems(int days, string filter) {
return _itemService.GetItems()
.Where(i => i.Time >= _dateTimeService.Now.AddDays(-days));
}
}
Also, the candidate should be asked to write a unit test, just to see they know how, for bonus points. Note if the candidate understands isolation for unit testing or does something that would work but be silly like generate the test data based on current date or duplicate the code logic in the test instead of working with static data.
Step 2: tell the candidate that the legacy code they need to fix looks a bit different:
public SomeClass {
public List<Item> GetItems(int days, string filter) {
var service = new ItemService(filter);
return service.GetItems()
.Where(i => i.Time >= DateTime.Now.AddDays(-days));
}
}
The ItemService now receives the filter as the parameter. Ask them what to do in this case.
The expected answer is a factory injected instead of the service, which will then be used to instantiate an IItemService with a parameter. Bonus discussion about software patterns can be inserted here.
There are other valid answers here, like using the DI container itself as a factory for the service, which might provoke interesting discussions in itself, like weighing constructor injection versus service provider in dependency injection and whether hybrid solutions might be better.
Bonus question: what if you cannot control the code of ItemService in step 1 and it does not implement an interface or a base class?
- warning, this might give a hint for the second part of the interview, so use it at the end
- correct answer 1: use the class as the type of the parameter and let the dependency container decide how to instantiate it
- correct answer 2: use a wrapper over the class that implements the interface and proxies to the instance methods.
Conclusion
For me this test told me a lot more about the candidate than just their dependency injection knowledge. We got to talking, I became aware of how their minds worked and I was both pleasantly surprised when they came with alternate solutions that kind of worked and a bit irked that they went that far and didn't see the superior option. Most of the time this made me think about the differences between what I would answer and what they did and this resulted in interesting discussions that enriched not only their experience, but also mine.
Dependency injection, separation of concerns and unit testing are important concepts for any modern developer. I hope this helps devs evolve and interviewers find the best candidates... at least until all of them get to read my blog.
Comments
Be the first to post a comment