My objective in preparing Poor Example was to explore the abilities and attitudes of developers beyond a narrow ability to remember facts. It is an exploration of the understanding Java developers have of good design principles and their natural inclination towards quality. It attempts to identify developers who care about the quality of their work beyond the functionality and into the extensibility and simplicity of their code.
Poor Example is a very simple application that contains some common design faults or limitations. The test instructions do not specify that the developer should do anything but add new functionality. The time constraint for the exercise is generous, which gives developers a choice between completing only what was asked for and no more, or taking a more proactive approach in fixing the design limitations and taking longer. Developers who care about quality will have the courage and pride in their work to do more than that requested. The issues should be reasonably self evident to any developer adding new functionality.
This is not a pass/fail kind of test, in that we are not looking for a well defined solution, but rather one that recognizes and addresses the design limitations. It informs us whether developers have a real and deeper appreciation of design principles and a commitment to quality over speed.
The initial repository can be found here. The solution can be found in the solution branch.
Evaluation of Applicant Solutions
This is not a test that can be evaluated without a good understanding of Java, in that the solution provided and the notes below do not represent the only possible solution. Below there are questions that should be asked about the applicants solution, getting a point for each one addressed.
Before getting into the detail of the solution offered check the following:
- That the code compiles and runs.
- That it produces the right output, similar to the solution.
Principle #1: Closed to Change, Open to Extension.
This is the Alert Bean which specifies the services to call when triggered. The original Alert class includes booleans representing each kind of service.
In order to add the new JMS service a new boolean would need to be added to the Alert class if the existing approach was followed. But ideally the Alert class should not need to be changed just because a new kind of service is introduced. In order to close Alert to modification we need to decouple this class from the services available. The solution Alert class therefore introduces a list of Senders, where Sender is an interface that will be implemented by the sending Services. All the boolean values are therefore no longer required, replaced with a list of Senders.
- Did the subject modify Alert to include a new boolean to handle the new service, or did they modify it so that it was no longer dependent on the detail of services available?
Principle #2: Dependency Injection
The original MainApplication creates the instances of the service classes it will use by itself. The whole point of using Spring and other dependency injection frameworks is that you can inject new implementations easily. This is very useful for testing, where you can isolate classes to test. By ignoring this principle and creating the classes internally there is no way to test the class in isolation.
- Did the subject identify the problem of dependency injection and remove the creation of the services from within MainApplication?
Principle #3: Separation of Concerns
The original MainApplication stores the configuration details of the services inside itself. It then uses various different mechanisms to transmit the configuration downstream to the various services. If the original approach is followed the new configuration information for JMS would need to be added to MainApplication.
However, the configuration details of each service is not the concern of the MainApplication, and should be moved out to the service classes themselves. Also, there is a block of if statements which call different services in different ways.
If the applicant follows this pattern they will need to add an if block. But by introducing a Sender interface and a single standard method call they can all be merged into a single call.
Did the subject identify that configuration is not the concern of MainApplication and move the configuration to the service classes?
Did the subject identify that the 'if statements' for each service type can be removed with the help of an Interface?
Did the subject remove dependencies in MainApplication on the service implementations?
Principle #4: Remove Circular Dependencies
DatabaseSender.java, EmailSender.java, WebServiceSender.java
The original examples had a classic circular dependency between the MainApplication class and the service classes DatabaseSender, EmailSender and WebServiceSender. Each of these sender classes uses a different mechanism to inject the configuration. Instead the configuration information should be moved into these sender classes.
Did the subject remove the dependency on MainApplication?
Did the subject move the configuration fields into the sender classes?
Using the Results
The worst possible result is an application which does not compile. The second worst is one that does not follow the output of the example solution. In other words they need to have completed a working version that implements the requested functionality. This is such as basic test in terms of functionality any java developer should be able to complete this within half an hour.
If they complete the test but go no further it is not necessarily a reflection of developer skill. It is not a reflection of their personal commitment necessarily either, but may be a result of a development culture where tasks are regimented and specifications are to be followed exactly. If a promising developer went no further it might be a plan to seek their input about what could be improved in the interview to see if they at least identified the issues.
Give applicants points for all the issues listed above, but also explore whether they made further improvements or observations and award points for these.
Conducting a post test interview with the applicant is crucial. It is not the intent of this test to be used alone to score candidates, but rather inform the employer so that they might be prepared to dig deeper in the interview.
That said a developer failing the basics will certainly be less skilled than one who identified and corrected all the issues. In my experience there has been a gulf between interviews where communication skills rather than development skills win the day, and recruitment processes which emphasize standard testing which tests knowledge but not development skill or attitude and exclude candidates who simply perform poorly in these forms of test.
And this is why I am emphasizing that even if you use this test or something like it, do not depend on it alone. Doing the test first followed by an interview gives you both a common frame of reference to discuss their experience.