The Essence vs. Ceremony issue plays out at both strategic and tactical levels, but the tactical examples are the easiest to follow and understand in a small example. I used to give a conference talk demo of refactoring from ceremony to essence, but I never set the example in prose. Chad Fowler is teaching today, and wanted to show the example, so here it is:
Step 0. Initial Code
public ActionForward edit(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
PersonForm personForm = (PersonForm) form;
if (personForm.getId() != null) {
PersonManager mgr =
(PersonManager) getBean("personManager");
Person person = mgr.getPerson(personForm.getId());
personForm = (PersonForm) convert(person);
updateFormBean(mapping, request, personForm);
}
return mapping.findForward("edit");
}
Step 1. Duck Typing
edit(mapping, form, request, response)
throws Exception {
personForm = form;
if (personForm.getId() != null) {
mgr = getBean("personManager");
person = mgr.getPerson(personForm.getId());
personForm = convert(person);
updateFormBean(mapping, request, personForm);
}
return mapping.findForward("edit");
}
Step 2. Once you are duck typing, the local variable personForm
is not needed.
edit(mapping, form, request, response)
throws Exception {
if (form.getId() != null) {
mgr = getBean("personManager");
person = mgr.getPerson(form.getId());
form = convert(person);
updateFormBean(mapping, request, form);
}
return mapping.findForward("edit");
}
Step 3. Return can be implicit (everything should return a value), and things can always go wrong (so there is no need to declare that an Exception
can be thrown).
edit(mapping, form, request, response) {
if (form.getId() != null) {
mgr = getBean("personManager");
person = mgr.getPerson(form.getId());
form = convert(person);
updateFormBean(mapping, request, form);
}
mapping.findForward("edit");
}
Step 4. Don't add a manager layer to MVC (yet). YAGNI.
edit(mapping, form, request, response) {
if (form.getId() != null) {
person = Person.find(form.getId());
form = convert(person);
updateFormBean(mapping, request, form);
}
mapping.findForward("edit");
}
Step 5. Conditionals make code expensive to test; so ceremonial conditionals are a particular nuisance. Let framework error handling deal with missing data on the form.
edit(mapping, form, request, response) {
person = Person.find(form.getId());
form = convert(person);
updateFormBean(mapping, request, form);
mapping.findForward("edit");
}
Step 6. All action methods have the same four arguments. Stop repeating yourself. Make them instance variables/methods on the controller instead.
edit() {
person = Person.find(form.getId());
form = convert(person);
updateFormBean(mapping, request, form);
mapping.findForward("edit");
}
Step 7. Let the model do double duty: backing the form and representing the database object. Adding a separate layer here is YAGNI until proven otherwise.
edit() {
person = Person.find(form.getId());
mapping.findForward("edit");
}
Step 8. Standard routing will already find the right view template, so don't be explicit.
edit() {
person = Person.find(form.getId());
}
Step 9. Rubyize the syntax.
def edit
@person = Person.find(params[:id])
end