i am working on a large application which has differnt conditions(ifs) and different methods associated with it.Please suggest a way to optimize below mentioned code(reduce as much of nested ifs as possible).I would like the code to able incorporate any other specific condition with ease.(I use property files to fetch conditions)
.
public getParameter(String parameter)
{
if(parameter=specific condition1
||parameter=specific condition2)
{
do this
}
if(parameter=specific condition3)
{
do something else
}
if(parameter=specific condition4)
{
do something else
}
if(parameter=general condition)
{
do something else
}
else {
do something else
}
Say you have a property file with
do1=val1,val2,val3
do2=val5,val6,val7
(it seems you have a fixed set of actions)
You may load it with
HashMap<String, HashSet<String>> rules = new HashMap<String, HashSet<String>>();
for(String key : properties.stringPropertyNames()) {
HashSet<String> set = new HashSet<String>();
set.addAll(Arrays.asList(properties.getProperty(key).split(",")));
rules.put(key, set);
}
Now you have a map linking action names ("do1", etc.) to sets of possible values ("val1", etc.).
You may execute the rules with
if (rules.get("do1").contains(parameter)) do1();
if (rules.get("do2").contains(parameter)) do2();
(I let you add the necessary checks to avoid null pointer exceptions for example)
you could use a switch case.
http://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
Related
This is my code which is working fine. But when I push the code, the sonarqube quality gate fails due to Cognitive Complexity. Any ideas on fixing this sonar issue
if (bbResponse.getEmails() != null && !bbResponse.getEmails().isEmpty()) {
bbResponse.getEmails().stream().forEach((BBEmail bbEmail) -> {
if ("CHK".equals(bbEmail.getSEQ())) {
//CODE
} else if ("CHT".equals(bbEmail.getSEQ())) {
//CODE
} else if ("MYT".equals(bbEmail.getSEQ())) {
//CODE
} else {
throw new IllegalStateException();
}
});
}
Some ideas (don't know whether they satisfy Sonarqube):
The test bbResponse.getEmails().isEmpty() isn't necessary. forEach() on an empty list is perfectly valid, will execute its body zero times.
As written in comments already, you can replace the string comparison conditionals by a switch statement.
You can refactor the lambda expression into a method of its own and use a method reference in the forEach() call.
You can refactor the blocks given as //CODE in your post, into methods of their own, if they are longer than a handful of lines.
By the way:
While Sonarqube surely gives valuable advice, I'd never make it a hard quality gate.
Having an automat with somewhat obscure rules decide on acceptable code style doesn't seem like a good idea to me. We all want clean, human-readable code, and that's not the same as Sonarqube-compatible code.
E.g. Sonarqube isn't able to judge the most important aspect of readability: the naming of classes, fields, variables and so on. And your issue shows that e.g. the complexity rule rejects code that no developer would ever judge as "difficult to read" (unless the "CODE" blocks that you omitted are overly long).
A check for non empty list may be redundant if you use stream(), _as well as forEach may be used without calling .stream()
It is possible to prepare a map of consumer methods for specific codes
Use Optional::ifPresentOrElse when getting a nullable consumer from the map or throw an exception.
static void handleEmails(List<BBEmail> emails) {
if (null != emails) {
Map<String, Consumer<BBEmail>> codes = Map.of(
"CHK", MyClass::processChkEmail,
"CHT", MyClass::processChtEmail,
"MYT", MyClass::processMytEmail
);
emails.orEach(bbEmail ->
Optional.ofNullable(codes.get(bbEmail.getSEQ()))
.ifPresentOrElse(consumer -> consumer.accept(bbEmail),
() -> {throw new IllegalStateException();}
)
);
}
}
static void processChkEmail(BBEmail email) {
// TODO CHK
}
static void processChtEmail(BBEmail email) {
// TODO CHT
}
static void processMytEmail(BBEmail email) {
// TODO MYT
}
This should resolve Sonar issue but I am not quite sure it increases readability for humans :)
Another previously mentioned option is switch statement -- which can be even more concise for Java 12+ syntax without break statements:
static void handleEmailSwitchJava12(List<BBEmail> emails) {
if (null != emails) {
emails.forEach(bbEmail -> {
switch (bbEmail.getSEQ()) {
case "CHK" -> processChkEmail(bbEmail);
case "CHT" -> processChtEmail(bbEmail);
case "MYT" -> processMytEmail(bbEmail);
default -> throw new IllegalStateException("Invalid SEQ code: " + bbEmail.getSEQ());
}
});
}
}
I have a code snippet like this
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
for (final SpecificationData data : target.getSpecifications()) {
if (StringUtils.isNotEmpty(data.getModelName())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getModelNumber())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getMaterial())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getColour())) {
productLinks.add(DETAILS);
break;
}
}
}
As you can see, I am iterating a collection and doing a check in order to populate the link "details" in front end. The idea is that I need to populate this link at least one of the attribute length inside current object should be > 0. Because of the fact, I have used so many break statement, this snippet is failing in sonar build process
What do I need? I request you guys to share me the simplest version of the above code or refactored code using latest JDK and yes we are using JDK 11 and I am not pretty sure about the methods that I need to use for this kind of check.
If there is no other alternatives how to overcome this "Loops should not contain more than a single "break" or "continue" statement" sonar issue.
Appreciate your time and effort on this.
The easiest solution could be just to join multiple if statements into one though it may not be helpful against Sonar rules :)
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
for (final SpecificationData data : target.getSpecifications()) {
if (StringUtils.isNotEmpty(data.getModelName())
|| StringUtils.isNotEmpty(data.getModelNumber())
|| StringUtils.isNotEmpty(data.getMaterial())
|| StringUtils.isNotEmpty(data.getColour())
) {
productLinks.add(DETAILS);
break;
}
}
}
However, you may use stream operations such as filter and findFirst like this without any for loop and break statements:
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
target.getSpecifications().stream()
.filter(x ->
Stream.of(x.getModelName(), x.getModelNumber(), x.getMaterial(), x.getColour())
.filter(StringUtils::isNotEmpty)
.findFirst()
.isPresent()
)
.findFirst()
.ifPresent(x -> productLinks.add(DETAILS));
}
UPDATE
For this specific case it is also possible to use flatMap to detect any first non-empty property and perform an action:
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
target.getSpecifications().stream()
.flatMap(x -> Stream.of(x.getModelName(), x.getModelNumber(), x.getMaterial(), x.getColour()))
.filter(StringUtils::isNotEmpty)
.findFirst()
.ifPresent(x -> productLinks.add(DETAILS));
}
I have just wrote a code to cach a table in the memory (simple java hashmap). Now one of the code that i am trying to replace is the find the objects based on criteria. it receives multiple field parameters and if those fields are not empty and not null, they were being added as part of hibernate query criteria.
To replace this, what i am thinking to do is
For each valid param (not null and no empty) I will create a HashSet which will satisfy this criteria.
Once i am done making hashsets for all valid criteria, I will call Set.retainAll(second_set) on all sets. So that at the end, I will have only that set which is intersection of all valid criteria.
Does it sound like the best approach or is there any better way to implement this ?
EDIT
Though, My original post is still valid and I am looking for that answer. I ended up implementing it in the following way. The reason is that it was kind a cumbersome with sets since after creating all sets, I had to first figure out which set is non empty so that the retainAll could be called. it was resulting in lots of if-else statements. My current implementation is like this
private List<MyObj> getCachedObjs(Long criteria1, String criteria2, String criteria3) {
List<MyObj> results = new ArrayList<>();
int totalActiveFilters = 0;
if (criteria1 != null){
totalActiveFilters++;
}
if (!StringUtil.isBlank(criteria2)){
totalActiveFilters++;
}
if (!StringUtil.isBlank(criteria3)){
totalActiveFilters++;
}
for (Map.Entry<Long, MyObj> objEntry : objCache.entrySet()){
MyObj obj = objEntry.getValue();
int matchedFilters = 0;
if (criteria1 != null) {
if (obj.getCriteria1().equals(criteria1)) {
matchedFilters++;
}
}
if (!StringUtil.isBlank(criteria2)){
if (obj.getCriteria2().equals(criteria2)){
matchedFilters++;
}
}
if (!StringUtil.isBlank(criteria3)){
if (game.getCriteria3().equals(criteria3)){
matchedFilters++;
}
}
if (matchedFilters == totalActiveFilters){
results.add(obj);
}
}
return results;
}
I have the following code:
private Facility updateFacility(Facility newFacility, Facility oldFacility) {
if (newFacility.getCity() != null)
oldFacility.setCity(newFacility.getCity());
if (newFacility.getContactEmail() != null)
oldFacility.setContactEmail(newFacility.getContactEmail());
if (newFacility.getContactFax() != null)
oldFacility.setContactFax(newFacility.getContactFax());
if (newFacility.getContactName() != null)
oldFacility.setContactName(newFacility.getContactName());
// ......
}
There are around 14 such checks and assignments. That is, except for a few, I need to modify all the fields of the oldFacility object. I'm getting a cyclomatic complexity of this code 14, which is "greater than 10 authorized" as per SonarQube. Any ideas upon how to reduce the cyclomatic complexity?
At some point in your program, you will have to implement the logic:
If the new facility has a property defined, update the old facility accordingly
If not, do not override the previous value from the old facility.
Without having a global look at your project, what you can do is to move that logic inside the setters of each property:
public class Facility {
public void setSomething(String something) {
if (something != null) {
this.something = something;
}
}
}
This way, your update method would simply be:
private Facility updateFacility(Facility newFacility, Facility oldFacility) {
oldFacility.setSomething(newFacility.getSomething());
// etc for the rest
}
I think you can apply Builder Pattern to resolve the issue, it may help you remove the frustration in the loop of if statement. Please see this link for more detials
You can override hashCode and equals methods in Facility class and do as follows:
if(!newFacility.equals(oldFacility))
{
//only when something is changed in newFacility, this condition will be excecuted
oldFacility = newFacility;
}
return oldFacility;
//This is just and example, you can return newFacility directly
NOTE : You can include all params or only those which decide the uniqueness. Its up to you.
Hope this helps!
You could copy the fields for the oldFacility object that you don't want to modify to some other variables, then update the whole oldFacility object, and just replace the fields that you didn't want to change with the content stored in the other variables. i.e.
private Facility updateFacility(Facility newFacility, Facility oldFacility){
String contentNotToBeModified; // or whatever variable type
contentNotToBeModified = oldFacility.getCity();
// Do the same for all data that you want to keep
oldFacility = newFacility;
newFacility.setCity(contentNotToBeModified);
}
So copy the data that you want to keep out of oldFacility first, then substitute oldFacility for newFacility, and replace the required attributes of newFacility with the data from oldFacility.
The not null check seems pointless to me since the NullPointerException won't be thrown if you slightly modify your example like this:
private Facility updateFacility(Facility newFacility, Facility oldFacility) {
if (newFacility != null) {
oldFacility.setCity(newFacility.getCity());
oldFacility.setContactEmail(newFacility.getContactEmail());
oldFacility.setContactFax(newFacility.getContactFax());
oldFacility.setContactName(newFacility.getContactName());
...
}
This will assign null values to references which were referencing to nulls anyway and will not cause any issues.
Assuming you were doing something like newFacility.getCity().toString() then the checks would be useful.
You could use Java Reflection for avoiding that copy/paste/write-same-Problem:
public Facility updateFacility(Facility newFacility, Facility oldFacility)
{
String[] properties = {"City", "ContactEmail", "ContactFax", "ContactName"};
for(String prop : properties) {
try {
Method getter = Facility.class.getMethod("get"+prop);
Method setter = Facility.class.getMethod("set"+prop, getter.getReturnType());
Object newValue = getter.invoke(newFacility);
if (newValue != null)
setter.invoke(oldFacility, newValue);
} catch (NoSuchMethodException |
SecurityException |
IllegalAccessException |
InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
...
}
Now you can simple change the properties[] array when there are new properties in the Facility class which you want to update that way.
EDIT: If you use the return type of the getter method to find the setter method, it is not neccessary to assume that the properties of Facility are all of the same type.
CAVEATS: Be careful in method renaming! This code will lead to runtime errors if you rename or remove methods from the Facility class. If you have to possibility to change the code of the Facility class, you should consider using an annotation to indicate which properties should be updated.
In my code I have a List<Person>. Attributes to the objects in this list may include something along the lines of:
ID
First Name
Last Name
In a part of my application, I will be allowing the user to search for a specific person by using any combination of those three values. At the moment, I have a switch statement simply checking which fields are filled out, and calling the method designated for that combination of values.
i.e.:
switch typeOfSearch
if 0, lookById()
if 1, lookByIdAndName()
if 2, lookByFirstName()
and so on. There are actually 7 different types.
This makes me have one method for each statement. Is this a 'good' way to do this? Is there a way that I should use a parameter or some sort of 'filter'? It may not make a difference, but I'm coding this in Java.
You can do something more elgant with maps and interfaces. Try this for example,
interface LookUp{
lookUpBy(HttpRequest req);
}
Map<Integer, LookUp> map = new HashMap<Integer, LookUp>();
map.put(0, new LookUpById());
map.put(1, new LookUpByIdAndName());
...
in your controller then you can do
int type = Integer.parseInt(request.getParameter(type));
Person person = map.get(type).lookUpBy(request);
This way you can quickly look up the method with a map. Of course you can also use a long switch but I feel this is more manageable.
If good means "the language does it for me", no.
If good means 'readable', I would define in Person a method match() that returns true if the object matches your search criteria. Also, probably is a good way to create a method Criteria where you can encapsulate the criteria of search (which fields are you looking for and which value) and pass it to match(Criteria criteria).
This way of doing quickly becomes unmanageable, since the number of combinations quickly becomes huge.
Create a PersonFilter class having all the possible query parameters, and visit each person of the list :
private class PersonFilter {
private String id;
private String firstName;
private String lastName;
// constructor omitted
public boolean accept(Person p) {
if (this.id != null && !this.id.equals(p.getId()) {
return false;
}
if (this.firstName != null && !this.firstName.equals(p.getFirstName()) {
return false;
}
if (this.lastName != null && !this.lastName.equals(p.getLastName()) {
return false;
}
return true;
}
}
The filtering is now implemented by
public List<Person> filter(List<Person> list, PersonFilter filter) {
List<Person> result = new ArrayList<Person>();
for (Person p : list) {
if (filter.accept(p) {
result.add(p);
}
}
return result;
}
At some point you should take a look at something like Lucene which will give you the best scalability, manageability and performance for this type of searching. Not knowing the amount of data your dealing with I only recommend this for a longer term solution with a larger set of objects to search with. It's an amazing tool!