Refactoring from Ceremony to Essence

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