I have a method having multiple if-else conditions (which is growing with each new msg-type support)
public Message<?> doTransform(Message<String> message) throws TransformationException {
try {
MessageBuilder<String> messageBuilder = null;
String payload = message.getPayload();
String payloadSubStr = payload.substring(0, Math.min(payload.length(), 100));
if(payloadSubStr.contains("<Management>")){
messageBuilder = buildManagementMsg(message);
} else if (payloadSubStr.contains("<Administration>") || (payloadSubStr.contains("<OtherAdministationAlert>"))){
messageBuilder = buildAdminMessages(message);
} else if (payloadSubStr.startsWith("Council")){
messageBuilder = parseCouncilMessages(message);
} else if (payloadSubStr.indexOf("Security") >= 0
|| payloadSubStr.indexOf("OtherSecurityAlert") >= 0){
messageBuilder = buildSecurityMessages(message);
} else if ( payloadSubStr.indexOf("<Staff>") >= 0
|| payloadSubStr.indexOf("<OtherStaffAlert>") >= 0){
messageBuilder = buildStaffMessages(message);
} else if(payloadSubStr.indexOf("<Student>") >= 0) {
messageBuilder = buildStudentMessages(message);
}else {
messageBuilder = buildOtherMessages(message);
}
return messageBuilder.build();
} catch(Exception e) {
throw new TransformationException(e);
}
}
Reason for doing substring: To avoid complete msg traversing with each if/else condition
Reason for using contains/indexOf combination: Messages received by this method can vary
Wanted to replace these if/else statements with some more cleaner logic. Not getting if Switch/Enum can be used or need to use any pattern as suggested over https://www.baeldung.com/java-replace-if-statements.
Gone through various similar questions available but not getting anything. Any suggestion will be helpful.
Thanks
If you are concerned with the number of conditions you'd need to add you could introduce a list of "message builder factories" (for lack of a better name atm) that you could append to.
A factory would contain a predicate to test the substring for and a messageBuilder(...) method. Then you'd iterate over the factory, check each of the predicates and if execute messageBuilder() on the first that matches.
Example:
interface MessageBuilderFactory<T> {
boolean test(T payload);
MessageBuilder<T> messageBuilder(Message<T> message);
}
class ManagementMBFactory implements MessageBuilderFactory<String> {
boolean test(String payload) {
return payload.contains("Management");
}
MessageBuilder<String> messageBuilder(Message<String> message) {
//content of buildManagementMsg() here
}
}
And in your code:
List<MessageBuilderFactory<String>> factories = ... //get the list of factories from somewhere
for( MessageBuilderFactory<String> factory : factories) {
if( factory.test(payloadSubStr) {
messageBuilder = factory.messageBuilder(message);
}
}
An advantage of doing it that way would be that the list of possible message builder factories is easily available and classes can be kept small (not all those buildXxx() methods in one single class).
Alternatively, if your message payload allows for that, you could actually try to parse it (it looks like XML) and operate on events, i.e. elements being found. That might be faster in the case of many small payloads and a huge number of possible message builders.
People are divided on the idea of multiple return statements in java but in this case I think I would tend to do that:
if(payloadSubStr.contains("<Management>")){
return buildManagementMsg(message);
}
if (payloadSubStr.contains("<Administration>") || (payloadSubStr.contains("<OtherAdministationAlert>"))) {
return buildAdminMessages(message);
}
if (payloadSubStr.startsWith("Council")){
return parseCouncilMessages(message);
}
if (payloadSubStr.indexOf("Security") >= 0
|| payloadSubStr.indexOf("OtherSecurityAlert") >= 0){
return buildSecurityMessages(message);
}
if ( payloadSubStr.indexOf("<Staff>") >= 0
|| payloadSubStr.indexOf("<OtherStaffAlert>") >= 0){
return buildStaffMessages(message);
}
if (payloadSubStr.indexOf("<Student>") >= 0) {
return buildStudentMessages(message);
}
return buildOtherMessages(message);
Going one step further this could be done with a validation service.
public class ValidationService {
public boolean isManagement(String str) { return str.contains("<Management>");
// ... and so on
}
And you can inject the service into the code such that you have
if (validationService.isManagement(payloadSubStr)) {
return buildManagementMsg(message);
}
// ...
For a case with conditions currently in OR you could use a list in the service, for example
public boolean isSecurity(String str) {
for (String term : new String[]{"Security", "OtherSecurityAlert"}) {
if (str.contains(term)) {
return true;
}
}
return false;
}
Related
I have the below utility method and I am using multiple if statements and getting cognitive complexity issue. I went through some links, but I am not able to understand how should I change my code without affecting users of this method.
public static boolean isWrapperValid(WrapperClass wrapper, boolean isTechnicalToken){
String key=null;
boolean isValidWrapper = false;
if (wrapper != null && wrapper.length() > 7
&& wrapper.substring(0, 6).equalsIgnoreCase("XYZ"))
{
wrapper= wrapper.substring(7, wrapper.lastIndexOf('.')+1);
}
if(wrapper != null && wrapper.equalsIgnoreCase("TFR")) {
isValidWrapper=Boolean.TRUE;
}
try {
key = wrapper.getKey();
}
catch (Exception exception) {
return isValidWrapper;
}
if(key!=null) {
Date tokenExpiryTime = key.getExpiresAt();
if(tokenExpiryTime!=null) {
return isValidWrapper;
}
String algorithm=key.getAlgorithm();
if(!DESIRED_ALGO.equals(algorithm)) {
return isValidWrapper;
}
String value6=key.getType();
if(!DESIRED_TYPE.equals(value6)) {
return isValidWrapper;
}
if(key.getValue1()!=null && key.getValue2().size()>0 && key.getValue3()!=null && key.getValue4()!=null && key.getValue5()!=null) {
isValidWrapper=Boolean.TRUE;
}
}
return isValidWrapper;
}
Please share your suggestions to refactor this code.
I don't think that merging many if conditions to one or simply do a code clean up, for example by changing the order of some instructions, can solve your problem.
Your code does not match the single responsibility principle. You should refactor this big method to smaller parts. Due to this it will testable, easier to maintain and read. I spent some time and did this:
public static boolean isWrapperValid(WrapperClass wrapper, boolean isTechnicalToken) {
final WrapperClass unpackedWrapper = unpackWrapper(wrapper);
boolean wrapperValid = isUnpackedWrapperValid(unpackedWrapper);
Key key = null;
try {
key = unpackedWrapper.getKey();
} catch (final Exception exception) {
return wrapperValid;
}
if (key != null) {
if (doesKeyMeetsBasicConditions(key)) {
return wrapperValid;
}
if (doesKeyMeetsValueConditions(key)) {
return true;
}
}
return wrapperValid;
}
protected static WrapperClass unpackWrapper(final WrapperClass wrapper) {
if (wrapper != null && wrapper.length() > 7 && wrapper.substring(0, 6).equalsIgnoreCase("XYZ")) {
return wrapper.substring(7, wrapper.lastIndexOf('.') + 1);
}
return wrapper;
}
protected static boolean isUnpackedWrapperValid(final WrapperClass wrapper) {
return wrapper != null && wrapper.equalsIgnoreCase("TFR");
}
protected static boolean doesKeyMeetsBasicConditions(final Key key) {
Date tokenExpiryTime = key.getExpiresAt();
if (tokenExpiryTime != null) {
return true;
}
String algorithm = key.getAlgorithm();
if (!DESIRED_ALGO.equals(algorithm)) {
return true;
}
String value6 = key.getType();
return !DESIRED_TYPE.equals(value6);
}
protected static boolean doesKeyMeetsValueConditions(final Key key) {
return key.getValue1() != null && key.getValue2().size() > 0
&& key.getValue3() != null && key.getValue4() != null
&& key.getValue5() != null;
}
I don't know the domain logic, so some of my methods have stupid names etc. As you can see, now you have a lot of smaller methods with not many branches (if conditions) - easier to test (a static code is not nice, but you can mock it by using for example PowerMock).
A bit of rewriting delivered a simplification, that still could be improved upon.
public static boolean isWrapperValid(WrapperClass wrapper, boolean isTechnicalToken){
if (wrapper != null && wrapper.length() > 7
&& wrapper.substring(0, 6).equalsIgnoreCase("XYZ"))
{
wrapper = wrapper.substring(7, wrapper.lastIndexOf('.')+1);
}
boolean isValidWrapper = wrapper != null && wrapper.equalsIgnoreCase("TFR");
try {
String key = wrapper.getKey();
if (key != null && key.getExpiresAt() == null
&& DESIRED_ALGO.equals(key.getAlgorithm())
&& DESIRED_TYPE.equals(key.getType())
&& key.getValue1() != null && !key.getValue2().isEmpty()
&& key.getValue3() != null && key.getValue4() != null
&& key.getValue5() != null) {
isValidWrapper = true;
}
}
catch (Exception exception) {
// DO NOTHING
}
return isValidWrapper;
}
After comment: here I catch any exception for all calls.
First of all, Sonar should give you more flags: reusing the wrapper parameter is usually a bad practice, NPE where invoking wrapper.getKey because wrapper can be null, but anyway, not the point...
Try reducing the number of if statements by creating local boolean variables (or possibly 1 big if statement if you have less than 5 or 6 tests, but often less readable). Once it's done, you should only have 1 block testing these boolean variables, and have one return statement, like the example above (not necessarily accurate!):
boolean expired = tokenExpiryTime != null;
boolean desiredAlgo = DESIRED_ALGO.equals(key.getAlgorithm());
boolean desiredType = DESIRED_TYPE.equals(value6);
if (expired || !desiredAlgo || !desiredType) {
return isValidWrapper;
}
However, your Cognitive complexity level seems pretty low if this kind of algorithm triggers it...
Another big way to reduce an algorithm complexity is to turn sub-blocks of code (loops, if and try-catch) into private methods. In your example, it could be something like a checkWrapperValidity method, responsible for every test returning isValidWrapper
I have some values in my Array list and I have to remove some particular values based on conditions.I am using IF condition but there are many conditions to compare so I need to optimize the comparison time.for e.g. my list is
Msisdn_array={45,85,79,60,502}
and if condition match then remove the entry.
Below are the Conditions, Is there any possible way to optimize this conditions.
if(Msisdn_array.contains("60") && Msisdn_array.contains("910"))
{
Msisdn_array.remove("60");
Msisdn_array.remove("910");
}
if(Msisdn_array.contains("75") && Msisdn_array.contains("500"))
{
Msisdn_array.remove("75");
Msisdn_array.remove("500");
}
if(Msisdn_array.contains("76") && Msisdn_array.contains("502"))
{
Msisdn_array.remove("76");
Msisdn_array.remove("502");
}
if(Msisdn_array.contains("61") && Msisdn_array.contains("911"))
{
Msisdn_array.remove("61");
Msisdn_array.remove("911");
}
if(Msisdn_array.contains("77") && Msisdn_array.contains("503"))
{
Msisdn_array.remove("77");
Msisdn_array.remove("503");
}
if(Msisdn_array.contains("78") && Msisdn_array.contains("505"))
{
Msisdn_array.remove("78");
Msisdn_array.remove("505");
}
if(Msisdn_array.contains("79") && Msisdn_array.contains("507"))
{
Msisdn_array.remove("79");
Msisdn_array.remove("507");
}
if(Msisdn_array.contains("62") && Msisdn_array.contains("912"))
{
Msisdn_array.remove("62");
Msisdn_array.remove("912");
}
if(Msisdn_array.contains("63") && Msisdn_array.contains("913"))
{
Msisdn_array.remove("63");
Msisdn_array.remove("913");
}
if(Msisdn_array.contains("64") && Msisdn_array.contains("914"))
{
Msisdn_array.remove("64");
Msisdn_array.remove("914");
}
if(Msisdn_array.contains("65") && Msisdn_array.contains("915"))
{
Msisdn_array.remove("65");
Msisdn_array.remove("915");
}
if(Msisdn_array.contains("66") && Msisdn_array.contains("916"))
{
Msisdn_array.remove("66");
Msisdn_array.remove("916");
}
if(Msisdn_array.contains("67") && Msisdn_array.contains("917"))
{
Msisdn_array.remove("67");
Msisdn_array.remove("917");
}
if(Msisdn_array.contains("68") && Msisdn_array.contains("918"))
{
Msisdn_array.remove("68");
Msisdn_array.remove("918");
}
if(Msisdn_array.contains("69") && Msisdn_array.contains("919"))
{
Msisdn_array.remove("69");
Msisdn_array.remove("919");
}
if(Msisdn_array.contains("70") && Msisdn_array.contains("920"))
{
Msisdn_array.remove("70");
Msisdn_array.remove("920");
}
if(Msisdn_array.contains("71") && Msisdn_array.contains("921"))
{
Msisdn_array.remove("71");
Msisdn_array.remove("921");
}
if(Msisdn_array.contains("72") && Msisdn_array.contains("922"))
{
Msisdn_array.remove("72");
Msisdn_array.remove("922");
}
if(Msisdn_array.contains("73") && Msisdn_array.contains("923"))
{
Msisdn_array.remove("73");
Msisdn_array.remove("923");
}
if(Msisdn_array.contains("74") && Msisdn_array.contains("924"))
{
Msisdn_array.remove("74");
Msisdn_array.remove("924");
}
if(Msisdn_array.contains("80") && Msisdn_array.contains("926"))
{
Msisdn_array.remove("80");
Msisdn_array.remove("926");
}
if(Msisdn_array.contains("81") && Msisdn_array.contains("927"))
{
Msisdn_array.remove("81");
Msisdn_array.remove("927");
}
if(Msisdn_array.contains("82") && Msisdn_array.contains("928"))
{
Msisdn_array.remove("82");
Msisdn_array.remove("928");
}
One potential optimization is that you could don't need to check if the second item is in the list. Instead just attempt to remove it. If it was removed, the remove method will return true and you can also remove the first item.
if(Msisdn_array.contains("60") && Msisdn_array.remove("910")){
Msisdn_array.remove("60");
}
If you don't want to write out each if statement, you could store the groups in a Map, with the first item as the key and the second item as the value.
Map<String, String> rules = new HashMap<>();
rules.put("60", "910");
rules.put("75", "500");
rules.put("76", "502");
...
...
for(Entry entry : rules.values()) {
if(Msisdn_array.contains(entry.getKey()) && Msisdn_array.remove(entry.getValue())){
Msisdn_array.remove(entry.getKey());
}
}
I think you only need to extract a method to check if all of a group values are existed in List and then remove all. For example:
private void removeIfAllExist(List<String> list, String[] values) {
for (String v : values) {
if (!list.contains(v)) {
return;
}
}
list.removeAll(Arrays.asList(values));
}
public void yourLogic() {
List<String> list = new ArrayList<>(Arrays.asList("45", "85", "79", "60", "502"));
String[][] conditions = new String[][]{
new String[]{"60", "910"},
new String[]{"75", "500"},
new String[]{"76", "502"},
new String[]{"61", "911"},
new String[]{"77", "503"},
// more conditions
};
for (String[] values : conditions) {
removeIfAllExist(list, values);
}
}
If you do not iterate through the list often, I suggest you use a Set.
Search in this collection is fast.
contains is an O(n) operation. The list is traversed until the element is found. So every time you call contains you are potentially traversing the entire list.
An optimization might be to traverse the list once, manually, and check if the elements exist, and then do your removes afterwards, at the cost of some extra memory to store the boolean variables:
boolean exists_72 = false;
boolean exists_922 = false;
for(String element : Msisdn_array) {
if(element.equals("72")) {
exists_72 = true;
} else if(element.equals("922")) {
exists_922 = true;
}
}
if(exists_72 && exists_922) }
Msisdn_array.remove("72");
Msisdn_array.remove("922");
}
as remove returns a boolean is sucessfully removed you could do
if (list.remove ("60") && list.remove ("90"))
{
// everything ok
}
else {
// put back "60"
list.add ("60");
}
but personally I would go for readability a just create a method
removeBoth (String one, String two) {
if(list.contains(one) && list.contains(two))
{
list.remove(one);
list.remove(two);
}
}
It's slow because of List.contains being slow and also because List.remove being even slower (as it must move all elements with bigger indexes in order to fill the gap). It's ugly because of code repetition.
Msisdn_array is against Java naming convention and it's no array, so lets call it inputList. Let's use a HashSet for the lookups and another one to track what should be removed.
class Worker {
private final Set<String> inputListAsSet = new HashSet<>();
private final Set<String> removals = new HashSet<>();
public static void process(List<String> inputList) {
final Worker worker = new Worker(inputList);
worker.removeIfBothPresent("60", "910");
worker.removeIfBothPresent("75", "500");
worker.removeIfBothPresent("76", "502");
worker.removeIfBothPresent("61", "911");
worker.removeIfBothPresent("72", "922");
worker.removeIfBothPresent("73", "923");
worker.removeIfBothPresent("74", "924");
worker.removeIfBothPresent("80", "926");
worker.removeIfBothPresent("81", "927");
worker.removeIfBothPresent("82", "928");
inputList.removeAll(worker.removals);
}
private Worker(List<String> inputList) {
inputListAsSet.addAll(inputList);
}
private void removeIfBothPresent(String first, String second) {
if (inputListAsSet.contains(first) && inputListAsSet.contains(second)) {
removals.add(first);
removals.add(second);
}
}
}
Instead of using a class instance, the sets could be passed as arguments, but creating a worker seems cleaner. Note that despite the optimizations, my code is shorter than the original. In case of duplicates, it's not exactly equivalent.
I have below method
public MsgEnum validateUser(String userId, String pwd, UserOperationEnum userOperatioEnum) {
try {
MstCredential mstUser = mstUserDAO.validateUser(userId);
if (null == mstUser) {
return MsgEnum.FG40010;
}
if (!pwd.equals(pUtil.decrypt(mstUser.getPassword()))) {
return MsgEnum.FG40010;
}
if (userOperatioEnum.getOprName().equals(mstUser.getOperation()) && mstUser.getStatus() == OperationStatusEnum.ACTIVE.getMsgCode()) {
return MsgEnum.FG20000;
}
return MsgEnum.FG50010;
}
catch(Exception e) {
LOGGER.error("Error occured while validateStoreUser: "+e.getMessage(),e);
MsgEnum.FG20020.setMsgDesc(MsgEnum.FG20020.getMsgDesc()+ e.getMessage());
return MsgEnum.FG20020;
}
}
I am getting this exception "The Cyclomatic Complexity of this method "validateUser" is 11 which is greater than 10 authorized."
How can I remove this exception?
You have to reduce the number of conditional branches of the method. Every condition increases the complexity.
So first, you should bundle the outcomes
if (null == mstUser) {
return MsgEnum.FG40010;
}
if (!pwd.equals(pUtil.decrypt(mstUser.getPassword()))) {
return MsgEnum.FG40010;
}
can be combined to
if (null == mstUser || !pwd.equals(pUtil.decrypt(mstUser.getPassword()))) {
return MsgEnum.FG40010;
}
but that does not yet remove the complexity, but makes further refactoring more simple.
Next step is refactor the conditions out into separeate method returning boolean
null == mstUser || !pwd.equals(pUtil.decrypt(mstUser.getPassword()))
to
boolean isPasswordValid(MstCredential mstUser, String pwd){
return null == mstUser || !pwd.equals(pUtil.decrypt(mstUser.getPassword()));
}
and
userOperatioEnum.getOprName().equals(mstUser.getOperation()) && mstUser.getStatus() == OperationStatusEnum.ACTIVE.getMsgCode()
to
boolean isOperationValid(MstCredential mstUser, UserOperationEnum userOperatioEnum){
return userOperatioEnum.getOprName().equals(mstUser.getOperation()) && mstUser.getStatus() == OperationStatusEnum.ACTIVE.getMsgCode();
}
So the final method looks like
public MsgEnum validateUser(String userId, String pwd, UserOperationEnum userOperatioEnum) {
try {
MstCredential mstUser = mstUserDAO.validateUser(userId);
if (isPasswordValid(mstUser, pwd)) {
return MsgEnum.FG40010;
}
if (isOperationValid(mstUser, userOperatioEnum)) {
return MsgEnum.FG20000;
}
return MsgEnum.FG50010;
}
catch(Exception e) {
LOGGER.error("Error occured while validateStoreUser: "+e.getMessage(),e);
MsgEnum.FG20020.setMsgDesc(MsgEnum.FG20020.getMsgDesc()+ e.getMessage());
return MsgEnum.FG20020;
}
}
if the complexity is still to high, you could further move the contents of the try-block into a separate method, returning a MsgEnum so the only concern of the method becomes to handle the exception.
since I don't have much details on how individual functions are called, you may want to create multiple functions (each for null value, wrong password and such) so that you do not have multiple execution paths in your function. Cyclomatic complexity of max 10 means your if-else or whatever other conditions cannot result in more than 10 ways to return from a function. In your case there are 11.
I am writing a text-based survival simulator that uses an array of Entitys. The Entity class contains data about each entity, such as energy, hydration, and morale. I'm starting to wrap up the project but I have a problem. In order to write a checkStatus() method, I need to have an if statement that checks for Entity[].isAlive on all entities, even if I don't know how long the array is. In short, how can I use an if statement to check for the value of all members of an array? I know I will probably have to use a for loop to iteratively check the members, with the array.getLength. So far I can only check variables in single classes. I have seen similar questions but they don't quite get what I'm looking for.
P.S. I'm using basic Java, with no frameworks or libraries.
Pseudo-code that demonstrates what I'm looking for
if Entity[ALL-MEMBERS].isAlive {
gameOver = true;
}
Java 6, 7, and 8:
public boolean areAllAlive(Entity[] entities) {
if(entities == null || entities.length == 0) {
return false; //?
}
for(Entity e : entities) {
if(!e.isAlive()) {
return false;
}
}
return true;
}
Java 8, using streams/functions:
public boolean areAllAlive(Entity[] entities) {
if(entities == null || entities.length == 0) {
return false; //?
}
return Arrays.stream(entities).allMatch(e -> e.isAlive());
}
First, since you probably don't know the number of Entities you are going to use before hand an ArrayList is probably a better choice. Then yes, you should use an enhanced for loop:
List<Entity> list = new ArrayList<>();
public void addEntities(){
//add Entities here
}
public boolean ifAlive(){
for (Entity e: list){
if (!e.isAlive()){
return false;
}
}
return true;
}
or something like that.
Assuming array is your entity's array, try this.:
for (int i = 0; i < array.length; i++) {
Entity entity = array[i];
if (entity.isAlive) {
gameOver = true;
//your code here
}
}
There are a lot of attempts to guess password in my application
public static boolean checkIfAddressBanned(InetSocketAddress from) {
String address = from.getAddress().getHostAddress();
if (bannedAddresses.contains(address)) {
if (loggingEnabled) {
log.info(String.format("IP [%s] is banned", address));
}
return true;
}
return false;
}
What I'm trying to do is to avoid multiple calls of log.info messages which degrade performance of the server. Code snippets above simple check boolean flag is logging is enabled. I would like to rewrite code above, to write to skip some of this events.
What is the less resource consuming way to skip some messages ? For example,
if (System.currentTimeMillis() % 3 == 0)
Another example
if (atomicLong.incrementAndGet % 3 == 0)
I don't know if you have control of the code defining bannedAddresses, but would something like this work for you?
private static Map<String, Integer> bannedAddresses = new HashMap<String, Integer>();
...
public static boolean checkIfAddressBanned(InetSocketAddress from) {
String address = from.getAddress().getHostAddress();
Integer attempts = bannedAddresses.get(address);
if (attempts == null) return false;
bannedAddresses.put(address, attempts+1);
if ( (loggingEnabled) && (attempts % 50 == 1) ) {
log.info(String.format("IP [%s] is banned, %d attempts", address, attempts));
}
return true;
}