0

I found some brilliant code with over 30 parameters given to a method (I lost the count). The implementation contains more than 500 lines with if/then/else and switch block's.

How could this be refactored in a clean way? What would your advise be on this?

A lot of implementations are all over the application and all push these parameters.

Problem method:

public static User findUser ( String userCode, String lastName, String firstName, String alternativeLastName, String sex, int day, int month, int year, String locationParameter, String locationInfo, Id groupId, Id organizationId, Id orderId, Id orderGroupId, Id orderOrganizationId, List<Id> groupIds, List<Id> organizationIds, List<Id> orderIds, List<Id> orderGroupIds, List<Id> orderOrganizationIds, String timeRange, Long daysAgo, Date dateFrom, Date dateUntil, CodingMap codingMap, List<String> languageList, String visitType, Account account, List<Group> accountGroups, Organization accountOrganization, Profile profile, AccessProfile accessProfile, Locale locale, int maxResults, long newTimeRange, long minimumTimeRange, boolean hideUserWithoutName, boolean withOrderInfo, boolean withVisitInfo, boolean orderEntryAvailable, boolean hideDiscontinuedResults, boolean checkPatientAccessRights, boolean searchForCopies, boolean inOrderEntry, boolean allPatientCodes, boolean addPatientCharacteristics, boolean showSpeciesColumn, boolean patientDefinedWithPrivacyLevel, boolean excludePatientsWithoutCodeInSelectedCodingSystem ) throws CompanyException { ... } 

Used all over the place like this:

User.findUser(member.getCode(), context.lastName, context.firstName, context.alternativeLastName, context.sex, context.birthDate.getDay(), context.birthDate.getMonth(), context.birthDate.getYear(), context.locationParameter, context.locationInfo, null, null, null, null, null, null, null, null, null, null, session.timeRange(), session.daysAgo(), session.dateFrom(), session.dateUntil(), order.codingMap(), order.languageList(), member.visitType(), null, null, null, null, null, locale, 25, 1000L,200L, session.point.booleanValue(), session.infobooleanValue(), false, true, true, true, true, true, true, true, false, false, false); 
3
  • 1
    My advice would be to RUN. Commented Aug 1, 2013 at 8:27
  • 1
    refactoring? I'd call it 'rewriting' the code :D Commented Aug 1, 2013 at 8:28
  • 49 parameters in fact !! Why would you have to know everything about a user (including his shoes size...) to find him in a db or any other persistent unit? Commented Aug 1, 2013 at 8:35

7 Answers 7

3

First things first, I'd examine its actual usage a little further.

If you find that it is repeatedly called with specific subsets of these parameters, you might be able to do something like:

User.FindUserByABC(A, B, C) User.FindUserByXYZ(X, Y, Z) 

If this is not suitable, as others have suggested I would create a SearchParams class. All properties would be initialised to default values and its usage would only involve setting the relevant search terms required for each call, e.g.:

SearchParams params = new SearchParams(){A = "...", Z = "..."} User.FindUser(params) 

The latter would certainly clean up the calling code at least, with minimal impact to the existing underlying mechanisms.

Sign up to request clarification or add additional context in comments.

Comments

2

You could create a Predicate interface (or use guava's):

interface Predicate<T> { boolean apply(T input); } 

and change your method to:

User findUser(Predicate<User> p) { //find user } 

You then call it like this:

findUser(new Predicate<User> () { public boolean apply(User user) { return member.getCode().equals(user.getCode()) && user.lastname.equals(context.lastname); //etc. } }); 

Next step could be to introduce a UserQuery using a fluent syntax to easily create those queries. It would then look like:

UserQuery query = new UserQuery(); query.lastname(context.lastname) .code(member.getCode()); //etc Predicate<User> p = query.build(); User user = findUser(p); 

Comments

2

Create a method class with a builder for all necessary parameters:

public class FindUser { // fields that represent necessary parameters private final String lastName; ... // fields that represent optional parameters private Id groupId; ... // builder class for necessary parameters public static class Builder { private String lastName; ... public Builder lastName(String lastName) { this.lastName = lastName; return this; } ... public FindUser build { return new FindUser(this); } } // constructor taking a builder with all necessary parameters private FindUser(Builder builder){ // check here, whether all fields are really set in the builder this.lastName = builder.lastName; ... } // setters for all optional parameters public FindUser groupId(Id groupId) { this.groupId = groupId; return this; } ... // the action public User compute() { ... } } 

Copy the former method body inside the new compute method of the method class. Afterwards you can refactor this method by extracting many chunks of code into their own methods and maybe classes.

The last two steps:

1) Inside the old findUser method, create a new object of the method class and call the compute method. That will not reduce the size of the parameter list.

2) Change all usages of the findUser method to use the new method class, then delete the old findUser method.

Usage will be:

FindUser fu = new FindUser.Builder() .lastname("last name") ... .build() .groupId(new GroupId()) ...; User user = fu.compute(); 

Comments

1

Encapsulate all these parameters in a class

class MethodParam { String userCode; String lastName; String firstName; String alternativeLastName; String sex; int day; int month; int year; String locationParameter; String locationInfo; Id groupId; Id organizationId; Id orderId; Id orderGroupId; Id orderOrganizationId; List<Id> groupIds; List<Id> organizationIds; List<Id> orderIds; List<Id> orderGroupIds; List<Id> orderOrganizationIds; String timeRange; Long daysAgo; Date dateFrom; Date dateUntil; CodingMap codingMap; List<String> languageList; String visitType; Account account; List<Group> accountGroups; Organization accountOrganization; Profile profile; AccessProfile accessProfile; Locale locale; int maxResults; long newTimeRange; long minimumTimeRange; boolean hideUserWithoutName; boolean withOrderInfo; boolean withVisitInfo; boolean orderEntryAvailable; boolean hideDiscontinuedResults; boolean checkPatientAccessRights; boolean searchForCopies; boolean inOrderEntry; boolean allPatientCodes; boolean addPatientCharacteristics; boolean showSpeciesColumn; boolean patientDefinedWithPrivacyLevel; boolean excludePatientsWithoutCodeInSelectedCodingSystem; } 

and then send that class to the method

findUser(MethodParam param) { .... } 

OR

put all the params in the map with a well defined constants as the keys

Map<String,Object> paramMap = new HashMap<>(); paramMap.put(Constants.USER_CODE, userCode); 

...

and accept the map as parameter

findUser(Map<String, Object> param) { .... } 

As far as the if/else, switch are concerned, they have to be divided into logical small sub methods to reduce the cyclometric complexity

Comments

1

You'd better create an object with all these parameters as atributes and deal with it all over the code.

 public class MyObeject{ String userCode, String lastName, String firstName, String alternativeLastName, String sex, int day, int month, int year, String locationParameter, String locationInfo, Id groupId, Id organizationId, Id orderId, Id orderGroupId, Id orderOrganizationId, List<Id> groupIds, List<Id> organizationIds, List<Id> orderIds, List<Id> orderGroupIds, List<Id> orderOrganizationIds, String timeRange, Long daysAgo, Date dateFrom, Date dateUntil, CodingMap codingMap, List<String> languageList, String visitType, Account account, List<Group> accountGroups, Organization accountOrganization, Profile profile, AccessProfile accessProfile, Locale locale, int maxResults, long newTimeRange, long minimumTimeRange, boolean hideUserWithoutName, boolean withOrderInfo, boolean withVisitInfo, boolean orderEntryAvailable, boolean hideDiscontinuedResults, boolean checkPatientAccessRights, boolean searchForCopies, boolean inOrderEntry, boolean allPatientCodes, boolean addPatientCharacteristics, boolean showSpeciesColumn, boolean patientDefinedWithPrivacyLevel, boolean excludePatientsWithoutCodeInSelectedCodingSystem } findUser(MyObject obj); 

And fill only the attributes you need in each case. I guess some of them will be optional in some cases.

Comments

0

I'd replace this with a small sql database with a simple primary key for each user.

Comments

0

Make builder with self-explaining setters? I understand that 30 setters is not much better, but you can at least generate them programmatically. And is it really that many necessary parameters?

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.