Writing Clean Code

Published Sep 14, 2017
Writing Clean Code

Writing code that just works is one thing — writing clean code is a whole new level. There is a saying, “Any fool can write code that a computer can understand. Good programmers write code that humans can understand,” and it makes perfect sense.

Good programmers should know how to write clean code that can be understood by others too, not just himself (or herself, feminists, please don't kill me). Clean code can be beneficial for a company but bad code can destroy a company. There are countless examples of bad code bringing companies down or making a disaster out of an otherwise good product.

One great example is space rockets being mislaunched due to a badly transcribed formula (a single line was missing that meant the rocket had to be terminated 293 seconds after launch, ultimately costing around 20 million dollars). The programmer's style can have a huge impact on any place he/she works for.

What's the delay? Let's learn how to write beautiful and clean code!

Examples of bad code:

  1. Has no idea what the exception is for.
try {
   /* open file */
}
catch(Exception e) {
  e.printStackTrace();
}

try {
   /* read file content */
}
catch (Exception e) {
  e.printStackTrace();
}

try {
   /* close the file */
}
catch (Exception e) {
  e.printStackTrace();
}
  1. Defining the logic using exceptions where a for-loop or any form of loop would be sufficient.
while(i < MAX_VALUE)
{
   try
   {
      while(true)
      {
         array[j] = //some operation on the array;
         j++;  

      }
   }
   catch(Exception e)
   {
      j = 0;
   }
}
  1. Some constructor that takes no parameter and creates a new instance of that class.

public MyClass() {
}

  1. If If If If If If If……… Seriously?!
if{
 if{
  if{
   if{
    if{
     if{
      if{
       if{
         ....
  1. And this:

String tmp = null;
// 50 lines that don't touch tmp
tmp = whatever;
// ...


  1. Calling an expensive function over and over instead of storing the result, when you know it won’t change.

if (expensiveFunction() > aVar)
    aVar = expensiveFunction();
for (int i=0; i < expensiveFunction(); ++i)
    System.out.println(expensiveFunction());
  1. Acute misapprehension of control flow.

void Next(state) {
  switch (state) {
  case INITIAL:
  DoThing1();
  break;
  case THING1:
  DoThing2();
  break;
  case THING2:
  DoThing3();
  break;
  case THING3:
  // do nothing -- exit
  break;
  }
  }

  void DoThing1() {
  // Do "thing 1"
  Next(THING1);
  }

  void DoThing2() {
  // Do "thing 2"
  Next(THING2);
  }

  void DoThing3() {
  // Do "thing 3"
  Next(THING3);
  }
  1. Hardcoded string.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello there This is some long ass hardcoded string" />
  1. Collection emptiness test using size()

if (List.size()=0){
    //Do something if its null
}
  1. Too many nested blocks.

if (){
    //Do something
}else if() {
    // Do something 
}else if() {
    // Do something 
}else if() {
    // Do something 
}else if() {
    // Do something 
}else if() {
    // Do something 
}... Oh come on!

These are some common forms of bad code. In my opinion, complicated code is bad form. If your code is complicated, it cannot be clean.

Complicated code is opaque. It’s hard to read, hard to understand, and impossible to debug.

Clean code requires that you take a complex problem and split it into simpler parts. The programmer should not create complicated solutions to complex problems.

The programmer should try to reduce complex problems to simple elements. The heart of the programmer's skill is working out precisely where to make each cut.

The most brilliant programmers destroy complication with precise targeted reduction. Eventually, each individual part is so simple that it is transparent. If those individual parts are also given the right names – and symbol names are chosen with care, the result is code that is elegant, transparent and obvious.

Now, lets discuss the techniques for fixing the bad code above.

  1. Specify the type of exception that your code throws and respond accordingly. Here is a solution for the bad code.

try {
   /* open file */
}
catch(FileNotFoundException e) {
  Log.e("File Open", e.getMessage());
}

try {
   /* read file content */
}
catch (IOException e) {
  Log.e("File Read", e.getMessage());
}

 try {
     /* close the file */ 
} 
catch (IOException e) {
   Log.e("File Close", e.getMessage());
}

  1. Just use a for loop in such cases.
for (int i=0; i<MAX_VALUE; i++){
    //some operation on the array;
}
  1. If you don’t need any parameter to initiate the class, you can just set up some static method for that class and call them.

E.g., you have class A.


class A{
    ...
    void function()
    {

    } 

}

If you declare the methods with the static modifier, i.e,


public static void function() { /* ... */ }

you can call them without creating instances: A.function()

  1. If you have multiple conditions, you can just perform OR or AND operations.

if (condition1 || condition2 || condition3 || condition4){
  //Do something
}
Or

if (condition1 && condition2 && condition3 && condition4){
  //Do something
}
  1. In these cases, you can declare the variable only when necessary.

  2. If you have some expensive method to call, you can just store its value in a variable and use it anytime you want.


var x=expensiveFunction();
if ( x> aVar)
aVar = x;
for (int i=0; i < x; ++i)
        System.out.println(x);
 
  1. You can just call the next method from that method. Here is a fix for that problem.

  void DoThing1() {
  // Do "thing 1"
  DoThing2();
  }

  void DoThing2() {
  // Do "thing 2"
  DoThing3();
  }

  void DoThing3() {
  // Do "thing 3"
  //do nothing - exit
  }
  1. Use values from string.xml.

  2. Test if the collection is null or empty. Calling the size of the collection gives nullPointerException if it is null. You might want to avoid that.


if(list == null || list.isEmpty()){
  //Do something
}
  1. Using nested blocks for two or three cases at maximum is mandatory, but using it for too many cases takes a lot of time. In such cases, you can use the Switch method.

switch(x){
    case 1:
        // do Something
        break;

    case 2:
        // do Something
        break;
    
    case 3:
        // do Something
        break;
    
    case 4:
        // do Something
        break;
    
    case 5:
        // do Something
        break;
    
    case 6:
        // do Something
        break;
}

Let's talk about some strategies that need to be applied while writing some beautiful code.

  1. Proper Exception Handling

A code is not guaranteed to run without any errors or exceptions. They can create exceptions we haven’t heard of before. In order to prevent our application crashing because of these exceptions, we need to handle them properly.

I suggest you try to use the try/catch/finally blocs.


try{  
    //Sensitive code  
} catch(ExceptionType e){  
    //Handle exceptions of type ExceptionType or its subclasses  
} finally {  
    //Code ALWAYS executed  
}

Try will allow you to execute sensitive code that could throw an exception.
Catch will handle a particular exception (or any subtype of this exception).
Finally will help to execute statements, even if an exception is thrown and not caught.

Here is an example:


for(File f : getFiles()){
    //You might want to open a file and read it
    InputStream fis;
    //You might want to write into a file
    OutputStream fos;
    try{
        handleFile(f);
        fis = new FileInputStream(f);
        fos = new FileOutputStream(f);
    } catch(IOException e){
        //Handle exceptions due to bad IO
    } finally {
        //In fact you should do a try/catch for each close operation.
        //It was just too verbose to be put here.
        try{
            //If you handle streams, don't forget to close them.
            fis.close();
            fos.close();
        }catch(IOException e){
            //Handle the fact that close didn't work well.
        }
    }
}

Using try/catch/final blocks alone won't make your code clean. Let's take a look at the following code.


try {
   /* open file */
}
catch(Exception e) {
  e.printStackTrace();
}

try {
   /* read file content */
}
catch (Exception e) {
  e.printStackTrace();
}

try {
   /* close the file */
}
catch (Exception e) {
  e.printStackTrace();
}

The above code will display an error message if anything goes wrong. However, we won't know which line of code is causing the error.

It might be either FileNotFoundException while opening the file or IOException while reading file content or while closing the file. The catch block here doesn't doing anything useful.

In fact, it hides useful information if the program were to crash. If you don’t catch the exception, you get a traceback. If you do catch it, you get much less.

Plus, it takes up five lines and makes the method almost twice as big as it needs to be. Doing without is a lot better. Example of proper exception handling:


try {
   /* open file */
}
catch(FileNotFoundException e) {
  Log.e("File Open", e.getMessage());
}

try {
   /* read file content */
}
catch (IOException e) {
  Log.e("File Read", e.getMessage());
}

 try {
     /* close the file */ 
} 
catch (IOException e) {
   Log.e("File Close", e.getMessage());
}

  1. Use Descriptive Variable Names

This rule can be hard to follow, but it makes perfect sense to you while reading or reusing your code. In general, variables should be named so that they make it clear what a variable contains. But, for some cases, short names like i for loop indexes or in for files is perfectly okay. But if the variable name is longer, please make it descriptive.

For example:


public class A{
    int x;
    String str1;
    String str2;
    String str3;
}

For the above class, of course we can store values there, but the names of those variables do not make sense. If you have to access the variable of object A, you might have to look into your class, know the name, and call the method, because you might get confused. Here is a better way to name the variable.


public class Data{
    int id;
    String title;
    String body;
    String link;
}

Here, the variable names are more clear and they also make sense.


public class Data{
    int id;
    String title;
    String body;
    String link;
}

There is an even better way:


public class InboxData{
    int msgId;
    String msgTitle;
    String msgBody;
    String msgLink;
}

Try to make names more descriptive so you don't get confused later. They are also easy to understand for other programmers too.

  1. Create better methods

Try to make atomic functions (do not repeat yourself).
Break it into small sections.
One method must perform only one thing.
Try to minimize the number of arguments.
Always use exception handling because showing error and halt is better than doing something and never finishing.
Avoid output arguments.
Do not offer anything extra. Methods must only do what the name suggests and nothing else.

  1. Do not store the return values you don't need.

While writing code, we assume that we might need the return value of that method for later use and store them. When we don't use them, we might confuse the other programmers who are reading our code.

Boolean available= User.isAvailable("23ftg") you could have written it as User.isAvailable("23ftg").

  1. Delete your trash code

It’s quite common to find code that’s commented out but still hanging around. This is mainly bad because it bloats the code unnecessarily. People seem to do this because they want to have the possibility to bring the code back, either because they are writing an alternative they are not sure about, or (and this seems quite common) because they don’t dare to delete it. However, in nearly all cases, there is no real reason to keep such code around. You should be using version control — that means you could always find any deleted code again. You can go to this link to learn about GitHub.

  1. Increase cohesion and decrease coupling.

Increased cohesion and decreased coupling lead to good software design.

Cohesion partitions your functionality so that it is concise and closest to the data relevant to it, whilst decoupling ensures that the functional implementation is isolated from the rest of the system.

Decoupling allows you to change the implementation without affecting other parts of your software.

Cohesion ensures that the implementation is more specific to functionality and at the same time easier to maintain.

The most effective method of decreasing coupling and increasing cohesion is design by interface.

Major functional objects should only ‘know’ each other through the interface(s) that they implement. The implementation of an interface introduces cohesion as a natural consequence.

While unrealistic in some scenarios, it should be a design goal to work toward.

Example (very sketchy):


public interface IStackoverFlowQuestion
      void SetAnswered(IUserProfile user);
      void VoteUp(IUserProfile user);
      void VoteDown(IUserProfile user);
}

public class NormalQuestion implements IStackoverflowQuestion {
      protected Integer vote_ = new Integer(0);
      protected IUserProfile user_ = null;
      protected IUserProfile answered_ = null;

      public void VoteUp(IUserProfile user) {
           vote_++;
           // code to ... add to user profile
      }

      public void VoteDown(IUserProfile user) {
          decrement and update profile
      }

      public SetAnswered(IUserProfile answer) {
           answered_ = answer
           // update u
      }
}

public class CommunityWikiQuestion implements IStackoverflowQuestion {
     public void VoteUp(IUserProfile user) { // do not update profile }
     public void VoteDown(IUserProfile user) { // do not update profile }
     public void SetAnswered(IUserProfile user) { // do not update profile }
}

Somewhere else in your codebase you could have a module that processes questions regardless of what they are:


public class OtherModuleProcessor {
    public void Process(List<IStackoverflowQuestion> questions) {
       ... process each question.
    }
}

I have listed some other points you might consider while writing your code:

Architecture – helps someone understand where your component or service fits.
Workflow – create one that illustrates the entry and exit points into your code.
Pseudo-code – write out your algorithm(s) before you get in front of an IDE.
Design – have technical leads review your design to vet any key decisions.
Documentation – draft your release notes first to focus on the end-user.
Code reviews – ask for them but also participate as a reviewer.
Testing – keep in mind that a QAE or script will validate and verify.
Debugging – give yourself tools to help during testing and post-release.
Exception Handling – plan for errors and think beyond the “happy path."
Corner Cases – look at load and edge scenarios that appear well after Day 1.
Metrics – define and incorporate them so you can improve things in v2.0.
Readability – use a clear convention for variables, functions, classes, etc.
Profiling – use tools to understand your 80/20 code execution path.
Maintenance – your code will outlast your tenure – think of the next guy.
Spacing – this is minor but clear begin/end syntax is easier to follow.

Discover and read more posts from Nawaraj Sharma
get started
Enjoy this post?

Leave a like and comment for Nawaraj

7
5
5Replies
Pragya Daga
6 days ago

Excellent. This is true that writing clean code gives the best view and the readability of that code gives best views in Android App Development. Thank a lot for posting this. This is very helpful to all.

manacy dasan
9 days ago

Useful post

Nawaraj Sharma
9 days ago

Thanks manacy

Alex Lee
11 days ago

Excellent article. Also read “Writing Solid Code” by Steve Maguire. It’s old but still very relevant. Once you become accustomed to writing code well it will increase your productivity immensely.

Nawaraj Sharma
9 days ago

Thank you Alex. I will check out that article :)

Show more replies

Get curated posts in your inbox

Read more posts to become a better developer