I am review my code using SonarQube. I am receiving the following issue.
Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.
But my method contains only 7 loops. Herewith I attached the code.
private void LayoutTouch(int touchType, int index) {
if (touchType != -1) { //+1
try {
ViewConfiguration vc = ViewConfiguration.get(ctContext);
mSlop = vc.getScaledTouchSlop();
parentLayout[index]
.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v,
MotionEvent event) {
if(!isValidEvent()){ //+2
return false;
}
if(checkTouchIndex(index)){ //+3
try{
// Code here
if (animationStarted) { //+4
return false;
}
final ViewConfiguration vc = ViewConfiguration
.get(getContext());
final int deltaX = (int) (event.getX() + 0.5f)
- mGestureCurrentX;
initiateVelocityTracker();
mVelocityTracker.addMovement(event);
mVelocityTracker
.computeCurrentVelocity(1000);
if(!doSwitchAndNeedToReturn(v, event, index, vc, deltaX)) // +5
return false;
}catch(Exception e){ //+6
setTouchProgressIndex(-1);
}finally{
setTouchProgressIndex(-1);
}
}
return false;
}
});
} catch (Exception e) { //+7
Log.e("Testing","Exception "+ e);
}
}
}
Why I am getting this issue. Please help me on this.
I agree with SonarQube that the code is overly complex.
Simplifications possible:
combine if statements
use a lambda
(not done here) use an extra method for the lambda code
So:
private void LayoutTouch(int touchType, int index) {
if (touchType != -1) { //+1
try {
ViewConfiguration vc = ViewConfiguration.get(ctContext);
mSlop = vc.getScaledTouchSlop();
parentLayout[index]
.setOnTouchListener((v, event) -> {
if (isValidEvent() && checkTouchIndex(index)) {
try{
// Code here
if (animationStarted) { //+4
return false;
}
final ViewConfiguration vc = ViewConfiguration
.get(getContext());
final int deltaX = (int) (event.getX() + 0.5f)
- mGestureCurrentX;
initiateVelocityTracker();
mVelocityTracker.addMovement(event);
mVelocityTracker
.computeCurrentVelocity(1000);
if (!doSwitchAndNeedToReturn(v, event, index, vc, deltaX)) // +5
return false;
} catch(Exception e) { //+6
setTouchProgressIndex(-1);
} finally {
setTouchProgressIndex(-1);
}
}
return false;
});
} catch (Exception e) { //+7
Log.e("Testing","Exception "+ e);
}
}
}
The extra method:
.setOnTouchListener(this::onTouch);
private boolean onTouch(View v, MotionEvent event) {
...
}
The checked exception handling is very unspecific. If not a specific exception can happen, maybe drop it (at the end).
Using member variables named with the prefix m, is not conventional in java. These variables indeed seem many, but with mouse, touch and so that might make sense.
I mention this, as the calculations seem refactorable.
But my method contains only 7 loops
Sonar is telling you that the method is hard to understand (cognitive complexity). And I do agree with the criteria. The complexity does not grow linearly and that is why it goes +1, +2, +3, +4 +5 +6 +7 = 28 > 21.
As a developer I would really want this piece of code cleaned up. Here are some suggestions:
Extract the OnTouchListener into a class (inner or not)
Change the initial check as a guard condition with an early return.
Review why are you doing the same thing in finally and the exception. setTouchProgressIndex(-1)
Related
I have a problem with sorting data in my project.
Since I implemented pagination I don't know how solve this issue.
Before pagination I fetched whole list of entities and sort it by this class:
public class EntitySorter {
private static final Logger LOG = Logger.getLogger(EntitySorter.class);
public static int sort(String s1, String s2) {
if (StringUtils.isBlank(s1) || StringUtils.isBlank(s2)) {
return -1;
}
if (!s1.contains("/") || !s2.contains("/")) {
return -1;
}
if (s1.substring(s1.lastIndexOf("/") + 1).length() != 4 ||
s2.substring(s2.lastIndexOf("/") + 1).length() != 4) {
return -1;
}
final String year1 = s1.substring(s1.lastIndexOf("/") + 1);
final String year2 = s2.substring(s2.lastIndexOf("/") + 1);
if (!NumberUtils.isDigits(year1) || !NumberUtils.isDigits(year2)) {
return -1;
}
final int result = NumberUtils.toInt(year1) - NumberUtils.toInt(year2);
if (result != 0) {
return result;
}
final String caseNumber1 = s1.substring(0, s1.indexOf("/"));
final String caseNumber2 = s2.substring(0, s2.indexOf("/"));
if (!NumberUtils.isDigits(caseNumber1) && NumberUtils.isDigits(caseNumber2)) {
try {
final int intCaseNumber1 = Integer.parseInt(caseNumber1.replaceAll("[^0-9]", ""));
return intCaseNumber1 - Integer.parseInt(caseNumber2);
} catch (NumberFormatException e) {
LOG.error(e.getMessage(), e);
}
return -1;
}
if (NumberUtils.isDigits(caseNumber1) && !NumberUtils.isDigits(caseNumber2)) {
try {
final int intCaseNumber2 = Integer.parseInt(caseNumber2.replaceAll("[^0-9]", ""));
return Integer.parseInt(caseNumber1) - intCaseNumber2;
} catch (NumberFormatException e) {
LOG.error(e.getMessage(), e);
}
return -1;
}
if (!NumberUtils.isDigits(caseNumber1) && !NumberUtils.isDigits(caseNumber2)) {
try {
final int intCaseNumber1 = Integer.parseInt(caseNumber1.replaceAll("[^0-9]", ""));
final int intCaseNumber2 = Integer.parseInt(caseNumber2.replaceAll("[^0-9]", ""));
return intCaseNumber1 - intCaseNumber2;
} catch (NumberFormatException e) {
LOG.error(e.getMessage(), e);
}
return -1;
}
return NumberUtils.toInt(caseNumber1) - NumberUtils.toInt(caseNumber2);
}
}
Let's take some example:
We have a list of IDs:
101/2021
102/2021
1/2022
86/2020
Correct sorted list is:
1/2022
102/2021
101/2021
86/2020
In database this ID is one column. It's not split to number and year. I tried to use Sort.by() but I didn't make a success. How can I use pagination and keep correct sorting?
For pagination to work optimally, the data should be indexed correctly..
If there is a different representation of the data you can use with one column then it's the best way.
If not then the easy way would just to decompose one column to multiple columns, create a multi column index and sort by these columns + you need to understand if the natural ordering of the columns fits your logic.
The hard way would be to create user defined function and index on it and other solutions, but I would avoid the unnecessary complexity.
Keep it simple!
I have a log() method to avoid try catch statement in forEach() below which was working in other code.
public <T> Consumer<T> log(LogConsumer<T, Throwable> logConsumer)
{
return i -> {
try
{
logConsumer.accept(i);
}
catch (Throwable e)
{
log("e = " + e);
}
};
}
#FunctionalInterface
public interface LogConsumer<T, E extends Throwable> {
void accept(T t) throws E;
}
Now I just want to use log in forEach below but I have the red rippled line in LINE such that
new Task.runJob(job, type))
I have red rippled line under job, type in
"runJob(Job, JobType) in Task cannot be applied to (java.lang.Object, < lambda parameter>)"
Now sure how to fix it to use log in forEach just to avoid
try-catch inside of it.
execute() {
Map<Job, JobType> map = getJobMap();
map.forEach( log((job, type)-> new Taks().runJob(job,type)) ); // LINE: error here
}
class Task {
public String runJob(Job job, JobType type) throws Exception
{
...
return result;
}
}
It happens because you cannot execute functions that throw exceptions using lambda expressions. You have to handle the exception using try-catch block. However, in order for your code to look more readable, create a function, that will handle the exception and return the desired result.
class Task {
public String runJob(Job job, JobType type)
{
try {
...
return result;
} catch (Exception e) {
log.error(e.getMessage());
}
return null;
}
}
In case if you care what will be the result, map it and filter for the result of your function is not null, otherwise, ignore it, but watch logs for any errors.
And then call it like shown below.
Notice: both ways work below, but the second way is more robust because you can handle the scenario when not all jobs were executed without exception.
execute() {
Map<Job, JobType> map = getJobMap();
// First way
map.forEach( log((job, type) -> new Taks().runJob(job,type)) );
// Another way
List<Object> batchResult = map.entrySet().stream()
.map((job, type) -> new Task().runJob(jon, type))
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (batchResult.size() == map.size()) {
// everythings is ok (all operations resulted in non-null result
} else {
// Have to study logs and figure out what went wrong
}
}
Looking at the code I recently wrote made me wonder:
public void process(Deque<OperandToken> stack, EvaluationConfig context) {
OperandToken a;
OperandToken b;
try {
b = stack.pop();
a = stack.pop();
} catch (NoSuchElementException e) {
throw new EvaluationException("Syntax error: not enough operands");
}
if (!a.isValueTypeOf(Number.class) || !b.isValueTypeOf(Number.class)) {
throw new EvaluationException("Syntax error...");
}
// more actions on a and b and finally stack.push(result)
}
Same but with catch in the end:
public void process(Deque<OperandToken> stack, EvaluationConfig context) {
try {
OperandToken b = stack.pop();
OperandToken a = stack.pop();
if (!a.isValueTypeOf(Number.class) || !b.isValueTypeOf(Number.class)) {
throw new EvaluationException("Syntax error...");
}
// more actions on a and b and finally stack.push(result)
} catch (NoSuchElementException e) {
throw new EvaluationException("Syntax error: not enough operands");
}
}
Is there any rule about preferred method, or some arguments (e.g. performance) that the first / the second style should be used? In both cases NoSuchElementException is the only exception that can appear, but the question is also valid for cases when there are multiple exceptions that can be thrown on different lines.
Google returns lots of people with deadlock issues in C3P0, but none of the solutions appear to apply (most people suggest setting maxStatements = 0 and maxStatementsPerConnection = 0, both of which we have).
I am using a ComboPooledDataSource from C3P0, initialised as;
cpds = new ComboPooledDataSource();
cpds.setDriverClass("org.postgresql.Driver");
cpds.setJdbcUrl("jdbc:postgresql://" + host + ":5432/" + database);
cpds.setUser(user);
cpds.setPassword(pass);
My query function looks like;
public static List<Map<String, Object>> query(String q) {
Connection c = null;
Statement s = null;
ResultSet r = null;
try {
c = cpds.getConnection();
s = c.createStatement();
s.executeQuery(q);
r = s.getResultSet();
/* parse result set into results List<Map> */
return results;
}
catch(Exception e) { MyUtils.logException(e); }
finally {
closeQuietly(r);
closeQuietly(s);
closeQuietly(c);
}
return null;
}
No queries are returning, despite the query() method reaching the return results; line. The issue is that the finally block is hanging. I have determined that the closeQuietly(s); is the line that is hanging indefinitely.
The closeQuietly() method in question is as you would expect;
public static void closeQuietly(Statement s) {
try { if(s != null) s.close(); }
catch(Exception e) { MyUtils.logException(e); }
}
Why would this method hang on s.close()? I guess it is something to do with the way I am using C3P0.
My complete C3P0 configuration (almost entirely defaults) can be viewed here -> http://pastebin.com/K8XDdiBg
MyUtils.logException(); looks something like;
public static void logException(Exception e) {
StackTraceElement ste[] = e.getStackTrace();
String message = " !ERROR!: ";
for(int i = 0; i < ste.length; i++) {
if(ste[i].getClassName().contains("packagename")) {
message += String.format("%s at %s:%d", e.toString(), ste[i].getFileName(), ste[i].getLineNumber());
break;
}
}
System.err.println(message);
}
Everything runs smoothly if I remove the closeQuietly(s); line. Both closing the ResultSet and Connection object work without problem - apart from Connection starvation of course.
Here's a piece of code we've all written:
public CustomerTO getCustomerByCustDel(final String cust, final int del)
throws SQLException {
final PreparedStatement query = getFetchByCustDel();
ResultSet records = null;
try {
query.setString(1, cust);
query.setInt(2, del);
records = query.executeQuery();
return this.getCustomer(records);
} finally {
if (records != null) {
records.close();
}
query.close();
}
}
If you omit the 'finally' block, then you leave database resources dangling, which obviously is a potential problem. However, if you do what I've done here - set the ResultSet to null outside the **try** block, and then set it to the desired value inside the block - PMD reports a 'DD anomaly'. In the documentation, a DD anomaly is described as follows:
DataflowAnomalyAnalysis: The dataflow analysis tracks local definitions, undefinitions and references to variables on different paths on the data flow.From those informations there can be found various problems. [...] DD - Anomaly: A recently defined variable is redefined. This is ominous but don't have to be a bug.
If you declare the ResultSet outside the block without setting a value, you rightly get a 'variable might not have been initialised' error when you do the if (records != null) test.
Now, in my opinion my use here isn't a bug. But is there a way of rewriting cleanly which would not trigger the PMD warning? I don't particularly want to disable PMD's DataFlowAnomalyAnalysis rule, as identifying UR and DU anomalies would be actually useful; but these DD anomalies make me suspect I could be doing something better - and, if there's no better way of doing this, they amount to clutter (and I should perhaps look at whether I can rewrite the PMD rule)
I think this is clearer:
PreparedStatement query = getFetchByCustDel();
try {
query.setString(1, cust);
query.setInt(2, del);
ResultSet records = query.executeQuery();
try {
return this.getCustomer(records);
} finally {
records.close();
}
} finally {
query.close();
}
Also, in your version the query doesn't get closed if records.close() throws an exception.
I think that DD anomaly note is more bug, than a feature
Also, the way you free resources is a bit incomplete, for example
PreparedStatement pstmt = null;
Statement st = null;
try {
...
} catch (final Exception e) {
...
} finally {
try{
if (pstmt != null) {
pstmt.close();
}
} catch (final Exception e) {
e.printStackTrace(System.err);
} finally {
try {
if (st != null) {
st.close();
}
} catch (final Exception e) {
e.printStackTrace(System.err);
}
}
}
moreover this is not right again, cuz you should close resources like that
PreparedStatement pstmt = null;
Throwable th = null;
try {
...
} catch (final Throwable e) {
<something here>
th = e;
throw e;
} finally {
if (th == null) {
pstmt.close();
} else {
try {
if (pstmt != null) {
pstmt.close();
}
} catch (Throwable u) {
}
}
}