
Some friends of mine like to meet up regularly and discuss code and on Friday I caught up with Fredy and we discussed keeping Ruby code clean and dry by orienting it East. Fredy showed me the following Cucumber steps from his project as a starting point for a discussion:
1 Given /the Customer results table is sorted by Address/ do
2 @customer_page.results.header.address.click
3 end
4
5 Given /the Customer results table is sorted by Surname/ do
6 @customer_page.results.header.surname.click
7 end
We both liked the use of the page fixture “@customer_page” as it encapsulated the page being worked with and kept the steps focused on the business logic rather than how to interact with classes that manipulate web pages or swing apps. However, the rest of the lines were not good in my opinion, as they exposed too much information on the ‘how’.
This is how the discussion went:
James: What is line 2 meant to do ?
Fredy: It sorts the customer page results by address by clicking on the address column in the header row.
James: So why not have a method like ’sort_by_address’ and let it take care of the how by getting the results, it would be more readable* ?
Fredy: But what is there (on line 2) is readable.
James: Which of them ‘results.header.address.click’ or ’sort_by_address’ conveys the business intent?
Fredy: I guess ’sort_by_address’ does, but it is only one line and it is done.
James: Sure but it isn’t East Oriented and therefore you are missing opportunities to keep the code clean and dry. See line 6, where you repeat yourself.
Fredy: You and your East thing. (In a sarcastic tone) Ok, tell me how East will help as Im dying to know?
James: For a start you are asking the object with the information for that information rather than telling the object with the information to do the work.
Fredy: But I am using ‘Tell Don’t Ask’, I’m telling the customer_page to give me the results.
James: That isn’t ‘Tell Don’t Ask’ as I understand it, which is also why I call it East and have rules to ensure code is East Oriented. With East you can’t have a public method that returns a value like results, so you would have to ask the customer_page to do something else, maybe ’sort_by_address’ ?
Fredy: Some code has to get the results and sort them !
James: Yes, but it should not be a public method, since this leads to duplicate code like you have (lines 2 & 6) because when you ask for an object then manipulate it the chances that more than one piece of code will do the same manipulation is made higher since most people wont look through the codebase to find the same manipulation and make it common. By going East you greatly reduce this possibility and put the functionality in an Object where it can be refactored to remove duplication.
Fredy: Lets assume line 2 and 6 are East as you suggest, how is that removing duplication and besides now I have an explosion of methods on my page fixture object ?
10 Given /the Customer results table is sorted by Address/ do
11 @customer_page.sort_by_address
12 end
13
14 Given /the Customer results table is sorted by Surname/ do
15 @customer_page.sort_by_surname
16 end
James: Now the methods are all on the same object and not spread across lots of other objects making the opportunity to refactor them into a single method more obvious. For example, ’sort_by_address’ can become ’sort_by’ and take an argument indicating the sort criteria. So your code can now look like this:
20 Given /the Customer results table is sorted by (.*?)/ do |criteria|
21 @customer_page.sort_by criteria
22 end
James: Which is East, reveals the business intent and provides a generic step that can be reused wherever a Customer results table needs sorting without you adding more code. If you follow these changes through the code you should see that going East made the code more DRY and reduced the count of public methods on several objects. As a bonus if the Business decide they want to sort using a keystroke rather than a click you only have to change the code in one spot.
Fredy: I guess I could give the ‘East’ thing a try. Would you help me ?
James: Sure.
*I made a mistake here in the discussion by using a value word like ‘more’ since what is actually more readable to one person may be less readable to another. I need to remind myself not to use ‘value’ words like ‘more’ or ‘better’ in these sorts of discussions.