Picture of stu

Refactoring from Ceremony to Essence

  • Posted By Stuart Halloway on April 23, 2008

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
Comments
  1. Evan LightApril 23, 2008 @ 09:36 PM

    If that isn’t a damn fine sales pitch from J2EE web apps to Rails then I don’t know what is.

  2. AnthonyApril 24, 2008 @ 03:07 AM

    Wow.

    Of course, the Java guys will cry foul—you can’t use Struts 1, no fair! Use Struts 2/JSF/Seam/Spring MVC/Tapestry/Stripes/GWT/Rife/Wicket ad nauseum. And they’ll cry that you’re ignoring the benefits provided by all that extra typing (pun intended).

    I find that as I distance myself more and more from Java, the distinctions between all its frameworks seem more and more quaint.

    Rails may have its faults. And yes, for some contexts, there maybe better alternatives out there (although I don’t count any from the Java world among them).

    But if anything, Rails and Ruby have taught me what it really means to have code that speaks. And it’s opened my eyes to so many more languages and frameworks outside of the provincial world of Java.

  3. Ryan ShriverApril 25, 2008 @ 04:29 AM

    Great post Stu, thanks for sharing. Reminded me why I’m loving writing ruby these days. It’s simplicity is a breath of fresh air from Java.

  4. jelmerApril 25, 2008 @ 09:28 PM

    I am not quite sure what point you are trying to make here. I believe most people would agree that the ruby end-result is cleaner than the original Java code. However you make it very easy for the casual reader to attribute this entirely to ruby on rails. While in reality you are doing two very distinct things.

    a) rewriting the java example in ruby b) changing the architecture of the code

    I believe tThe most benefit is derived from the change in architecture. Obviously using a service layer and data transfer objects is overkill for such a trivial example. Had the Java code been written using a similar approach it might have looked like this in struts 2:

    public void prepare() throws Exception { person = jpaTemplate.find(Person.class, id); }

    Or like this in spring mvc:

    protected Object formBackingObject(HttpServletRequest req) throws Exception { return jpaTemplate.find(Person.class, ServletRequestUtils.getRequiredLongParameter(req, “id”)) }

  5. StuApril 26, 2008 @ 03:01 AM

    jelmer: The nice thing about breaking the refactoring into steps, and explaining each step, is that you get an a la carte meal. You don’t have to agree with all of them. You can (and should) decide that some are way more important than others. To me the important thing is deciding what “essence” means to you, and living that decision in your code. Get there in whatever language you like, just get there.

  6. jelmerApril 26, 2008 @ 08:38 AM

    Using this rationale you shouldn’t have used MVC (yet). YAGNI

    Clearly fetching the object from the view would have been closer to the “essence”

    Of course I wouldn’t recommend doing that for 95% of all applications. but ten again i wouldn’t recommend removing the service layer for most of those applications as well

  7. DaveMay 13, 2008 @ 01:46 AM

    Great post! We consult daily in both Java and Ruby and I think this just sums up perfectly the contrast between the two languages (and associated frameworks). Ruby lowers the impedence mismatch between brain and working code.

  8. Ola BiniMay 13, 2008 @ 02:49 AM

    Stu: Nice breakup of the steps.

    jelmer: Not sure if that comment is tounge-in-cheek. Of course MVC is never YAGNI. If you for some reason is serious: MVC is never YAGNI for the same reason that using a high-level language is never YAGNI. You could argue that you should code everything in assembler until you know you will need something better. That’s not really a valid line of reasoning, and MVC definitely falls in the same category of “by-default” choice that is something you need to argue AGAINST, not FOR.

  9. David WaiteMay 13, 2008 @ 08:14 AM

    jelmer: struts2 moves the ceremony out of your code into surrounding code. i.e.

    /* import and package statements */

    public class PersonExample extends ActionSupport implements Preparable {

    private int id; private JpaTemplate jpaTemplate; private Person person;

    public void setId(int id) { this.id = id; }

    public void setJpaTemplate(JpaTemplate jpaTemplate) { this.jpaTemplate = jpaTemplate; }

    public Person getPerson() { return person; }

    public void prepare() throws Exception { person = jpaTemplate.find(Person.class, id); } // execute can be omitted, as we are inheriting from ActionSupport }

    This is arguably significantly less readable than the original example. I am also not showing the struts.xml or web.xml configuration to map this action to a URL or the success/error result codes to result handlers that is configured to render a page, the validation xml file to indicate that ‘id’ is a required parameter, or the spring framework setup for the JPA handler.

    The rails version is that method above, wrapped in a class. Five lines.

  10. Tom GMay 15, 2008 @ 02:03 PM

    It’s pretty amazing how fast things have changed in 4 years. First there was Struts, then Appfuse helped fix a lot of things (for me). Then Spring came and finally ActiveRecord with Ruby.

  11. Eddy YoungMay 21, 2008 @ 02:06 PM

    “Let the model do double duty.”

    Single. Responsibility. Principle.