Reduce the damn if else nesting in the code and make the code more concise!

Keywords: Android Programming Java SDK

Written in front

I wonder if you've ever encountered an if else nest like a "pyramid lying across":

if (true) {
    if (true) {
        if (true) {
            if (true) {
                if (true) {
                    if (true) {

                    }
                }
            }
        }
    }
}

I'm not exaggerating. I really met you! Nested 6, 7 layers, a function hundreds of lines, Jane! Straight! Look! Die! People!

As an indispensable conditional statement in every programming language, if else is widely used in programming. However, if else does not generally recommend nesting more than three layers. If there is too much if else nesting in a piece of code, the readability of the code will decline rapidly, and the difficulty of later maintenance will also be greatly improved. Therefore, we programmers should try to avoid too many if else nesting. Next, I'll talk about how I can reduce if else nesting in my work.

text

Before we talk about my approach, let's start with an example to illustrate the drawbacks of too much nesting in if else.

Imagine the next business requirement for simple sharing: support for sharing links, pictures, text and text, and share results back to users (in order not to digress, here's a brief business, which is much more complex). When you take over such a business, do you think it's easy to do it with a little brainstorming?

First, define the type of sharing, sharing beans and sharing callback classes:

private static final int TYPE_LINK = 0;
private static final int TYPE_IMAGE = 1;
private static final int TYPE_TEXT = 2;
private static final int TYPE_IMAGE_TEXT = 3;

public class ShareItem {
    int type;
    String title;
    String content;
    String imagePath;
    String link;
}

public interface ShareListener {

    int STATE_SUCC = 0;
    int STATE_FAIL = 1;

    void onCallback(int state, String msg);
}

ok, then define a sharing interface and share each type separately.

public void share (ShareItem item, ShareListener listener) {
    if (item != null) {
        if (item.type == TYPE_LINK) {
            // Share links
            if (!TextUtils.isEmpty(item.link) && !TextUtils.isEmpty(item.title)) {
                doShareLink(item.link, item.title, item.content, listener);
            } else {
                if (listener != null) {
                    listener.onCallback(ShareListener.STATE_FAIL, "Incomplete information sharing");
                }
            }
        } else if (item.type == TYPE_IMAGE) {
            // Sharing pictures
            if (!TextUtils.isEmpty(item.imagePath)) {
                doShareImage(item.imagePath, listener);
            } else {
                if (listener != null) {
                    listener.onCallback(ShareListener.STATE_FAIL, "Incomplete information sharing");
                }
            }
        } else if (item.type == TYPE_TEXT) {
            // Share text
            if (!TextUtils.isEmpty(item.content)) {
                doShareText(item.content, listener);
            } else {
                if (listener != null) {
                    listener.onCallback(ShareListener.STATE_FAIL, "Incomplete information sharing");
                }
            }
        } else if (item.type == TYPE_IMAGE_TEXT) {
            // Share text
            if (!TextUtils.isEmpty(item.imagePath) && !TextUtils.isEmpty(item.content)) {
                doShareImageAndText(item.imagePath, item.content, listener);
            } else {
                if (listener != null) {
                    listener.onCallback(ShareListener.STATE_FAIL, "Incomplete information sharing");
                }
            }
        } else {
            if (listener != null) {
                listener.onCallback(ShareListener.STATE_FAIL, "Unsupported sharing types");
            }
        }
    } else {
        if (listener != null) {
            listener.onCallback(ShareListener.STATE_FAIL, "ShareItem Can not be null");
        }
    }
}

So far, a simple sharing model has been developed. Is there any problem? To be honest, if there is no pursuit, there is really no problem, at least the idea is clear. But in a week? What about in a month? Or a year later? Share has 15 branches, which means that every time you look back at the code, you have to turn your brain into a miniature processor, considering 15 situations. If there are bug s, you have to consider 15 situations and test 15 cases. If you need to share more small video functions now, you have to add three more branches and change the code. It's not "open-closed" at all. If another project is transferred to others to follow up, and others will turn their brain into a processor to think about the role of each branch, I am sure that eighty percent of people will Tucao code.

Our programmers'minds should not be spent on endless branching statements, but on the business itself. So it is necessary to avoid writing multi-branch nested statements. Okay, let's analyze the reason why the code above is multi-branching:

1. Null Value Judgment
2. Business Judgment
3. State Judgment

Almost all businesses are inseparable from these judgments, which leads to excessive nesting of if else. Is that impossible to solve? The answer is definitely not.

The above code is written in java, for Java programmers, null value judgment is simply frustrating, physically and mentally exhausting. Each callback of the above code determines whether the listener is empty, whether the ShareItem passed in by the user is empty, and whether the fields in the ShareItem are empty...

In this case, my approach is simple: interface layering.

Reducing if else Method 1: Interface Layering

The so-called interface hierarchy refers to: dividing the interface into external and internal interfaces, all null value judgments are completed in the external interface only once; and the variables introduced by the internal interface are guaranteed not to be null by the external interface, thus reducing null value judgments.

Come on, look at the code more intuitively:

public void share(ShareItem item, ShareListener listener) {
    if (item == null) {
        if (listener != null) {
            listener.onCallback(ShareListener.STATE_FAIL, "ShareItem Can not be null");
        }
        return;
    }

    if (listener == null) {
        listener = new ShareListener() {
            @Override
            public void onCallback(int state, String msg) {
                Log.i("DEBUG", "ShareListener is null");
            }
        };
    }

    shareImpl(item, listener);
}

private void shareImpl (ShareItem item, ShareListener listener) {
    if (item.type == TYPE_LINK) {
        // Share links
        if (!TextUtils.isEmpty(item.link) && !TextUtils.isEmpty(item.title)) {
            doShareLink(item.link, item.title, item.content, listener);
        } else {
            listener.onCallback(ShareListener.STATE_FAIL, "Incomplete information sharing");
        }
    } else if (item.type == TYPE_IMAGE) {
        // Sharing pictures
        if (!TextUtils.isEmpty(item.imagePath)) {
            doShareImage(item.imagePath, listener);
        } else {
            listener.onCallback(ShareListener.STATE_FAIL, "Incomplete information sharing");
        }
    } else if (item.type == TYPE_TEXT) {
        // Share text
        if (!TextUtils.isEmpty(item.content)) {
            doShareText(item.content, listener);
        } else {
            listener.onCallback(ShareListener.STATE_FAIL, "Incomplete information sharing");
        }
    } else if (item.type == TYPE_IMAGE_TEXT) {
        // Share text
        if (!TextUtils.isEmpty(item.imagePath) && !TextUtils.isEmpty(item.content)) {
            doShareImageAndText(item.imagePath, item.content, listener);
        } else {
            listener.onCallback(ShareListener.STATE_FAIL, "Incomplete information sharing");
        }
    } else {
        listener.onCallback(ShareListener.STATE_FAIL, "Unsupported sharing types");
    }
}

As you can see, the above code is divided into external interface share and internal interface shareImpl. ShareItem and ShareListener's judgment are all done in share. ShareImpl reduces the nesting of if else, which is equivalent to allocating if else. As a result, the code is much more readable and nested in no more than three layers.

But as you can see, shareImpl still contains the judgment of sharing type, that is, business judgment. We all know how big the product manager's brain hole is, and the type of sharing can change or be added at any time. Well, when it comes to polymorphism, I'm sure we all want to use polymorphism. Polymorphism can be used not only to cope with business changes, but also to reduce the nesting of if else.

Reducing if El Se method 2: polymorphism

With polymorphism, each service is handled separately and no business judgment is made at the interface. ShareItem is abstracted as a basic class, and its subclasses are implemented for each business.

public abstract class ShareItem {
    int type;

    public ShareItem(int type) {
        this.type = type;
    }

    public abstract void doShare(ShareListener listener);
}

public class Link extends ShareItem {
    String title;
    String content;
    String link;

    public Link(String link, String title, String content) {
        super(TYPE_LINK);
        this.link = !TextUtils.isEmpty(link) ? link : "default";
        this.title = !TextUtils.isEmpty(title) ? title : "default";
        this.content = !TextUtils.isEmpty(content) ? content : "default";
    }

    @Override
    public void doShare(ShareListener listener) {
        // do share
    }
}

public class Image extends ShareItem {
    String imagePath;

    public Image(String imagePath) {
        super(TYPE_IMAGE);
        this.imagePath = !TextUtils.isEmpty(imagePath) ? imagePath : "default";
    }

    @Override
    public void doShare(ShareListener listener) {
        // do share
    }
}

public class Text extends ShareItem {
    String content;

    public Text(String content) {
        super(TYPE_TEXT);
        this.content = !TextUtils.isEmpty(content) ? content : "default";
    }

    @Override
    public void doShare(ShareListener listener) {
        // do share
    }
}

public class ImageText extends ShareItem {
    String content;
    String imagePath;

    public ImageText(String imagePath, String content) {
        super(TYPE_IMAGE_TEXT);
        this.imagePath = !TextUtils.isEmpty(imagePath) ? imagePath : "default";
        this.content = !TextUtils.isEmpty(content) ? content : "default";
    }

    @Override
    public void doShare(ShareListener listener) {
        // do share
    }
}

(Note: The construction method of each subclass above also does null value processing for each field, assigning default if null, so that if the user passes null value, problems will be found in debugging.)

When polymorphism is implemented, the shared interface is much simpler:

public void share(ShareItem item, ShareListener listener) {
    if (item == null) {
        if (listener != null) {
            listener.onCallback(ShareListener.STATE_FAIL, "ShareItem Can not be null");
        }
        return;
    }

    if (listener == null) {
        listener = new ShareListener() {
            @Override
            public void onCallback(int state, String msg) {
                Log.i("DEBUG", "ShareListener is null");
            }
        };
    }

    shareImpl(item, listener);
}

private void shareImpl (ShareItem item, ShareListener listener) {
    item.doShare(listener);
}

Hee hee, how about if else? Isn't it cool? If this sharing function is a function of your own App, not a third-party SDK, it's OK here. But if the third party shares SDK functions, the classes exposed to users increase a lot (sub-classes of ShareItem, equivalent to throwing if else to users), and the user's access cost increases, which violates the "Dimiter Principle".

It's easy to deal with this situation, just encapsulate one layer again. Reduce access to subclasses of ShareItem, define several methods in the main class exposed to users, and help users create specific sharing types internally, so that users do not need to know specific classes:

public ShareItem createLinkShareItem(String link, String title, String content) {
    return new Link(link, title, content);
}

public ShareItem createImageShareItem(String ImagePath) {
    return new Image(ImagePath);
}

public ShareItem createTextShareItem(String content) {
    return new Text(content);
}

public ShareItem createImageTextShareItem(String ImagePath, String content) {
    return new ImageText(ImagePath, content);
}

Or, some would say, users need to know a few more ways. Personally, I think it is better for users to know more methods than more classes, and the method name can tell the intention at a glance. The cost is still small and acceptable.

In fact, in this case, more people think of the use of factory mode. Well, factory mode can solve this problem (actually, users need to know a few more type s), but factory mode inevitably introduces branches, we can use Map to eliminate branches.

Reduce if else method 3: Use Map instead of branching statements

By pre-caching all shared types in Map, the specific types can be obtained directly by get, and the branches can be eliminated.

private Map<Integer, Class<? extends ShareItem>> map = new HashMap<>();

private void init() {
    map.put(TYPE_LINK, Link.class);
    map.put(TYPE_IMAGE, Image.class);
    map.put(TYPE_TEXT, Text.class);
    map.put(TYPE_IMAGE_TEXT, ImageText.class);
}

public ShareItem createShareItem(int type) {
    try {
        Class<? extends ShareItem> shareItemClass = map.get(type);
        return shareItemClass.newInstance();
    } catch (Exception e) {
        return new DefaultShareItem(); // Return the default implementation instead of null
    } 
}

This method has its own advantages and disadvantages compared with those of the above-mentioned methods, depending on your choice.~

Written in the end

What about the confiscation here? Summarize the ways to reduce if else:

  • The interface is divided into external and internal interfaces, and all null value judgments are performed on the external interface, while the variables transferred from the internal interface are guaranteed not to be null by the external interface, thus reducing null value judgments.
  • Using polymorphism to eliminate business judgments, each subclass pays attention to its own implementation, and implements the method of creating subclasses to avoid users knowing too many classes.
  • The branch status information is pre-cached in Map, and get the specific value directly to eliminate the branch.

Okay, that's all. I hope you can pay attention to the code later. If you want to avoid it, you can encourage it. I hope you can write more and more concise code~

Ending

Welfare at the end of the article!!


At the same time, after years of collecting, I have collected a complete set of learning materials and HD detailed Android Architecture Advanced Learning Maps and Notes for free to share with you, hoping to have some reference and help for friends who want to become architects.

Below is a screenshot of some of the information, sincere: especially suitable for Android programmers with development experience to learn.



Free access to information: Now pay attention to me and join the group chat
Group number: 887084983
Or click on the link to join the group chat [Android Architecture Design Group 4]: https://jq.qq.com/?_wv=1027&k=5DaNXoL

Well, that's the end of the article. If you think it's good, give it a compliment? If you think it's worth improving, please leave me a message. We will inquire carefully and correct the shortcomings. Thank you.

I hope you can forward, share and pay attention to me, and update the technology dry goods in the future. Thank you for your support! ___________

Forwarding + Praise + Concern, First Time to Acquire the Latest Knowledge Points

Android architects have a long way to go. Let's work together.


Finally, I wish you all a happy life.~

Posted by blacksmoke26 on Thu, 19 Sep 2019 21:26:44 -0700