Writing Clean Code : Part 3

Learn-to-Code-Gain-Independence-shutterstock_355779038-Lemberg-Vector

I have described some really bad codes in part1 and I have provided solutions to those codes in part2. Lets talk about some strategies that are needed to apply while writing some beautiful code.

1. Proper Exception Handling

266277_700b.jpg

A code is not certain to run without any error or exception. They can create exceptions we haven’t heard before. So in order to prevent our application to crash because of these exceptions we need to handle them properly.

I suggest you 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 which 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 catched.

Lets take 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.
        }
    }
}

But also, by just using try/catch/final block wont make your code clean. Lets take a look at 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();
}

Above code will display error message if anything goes wrong. But we wont 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 does nothing 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 5 lines, and it makes the method almost twice as big as it needs to be. Doing without is just 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());
}

2. 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, in for files is perfectly okay. But if the variable name is longer then please make it descriptive.

For Example:

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

In above class ofcourse we can store values there but the names of those variables does not make sense. If you have to access the variable of object A then 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 makes good sense.

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

There is more better way than that

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

Try to make names more descriptive so you dont get confused later. They are also easy to understand by other programmers too.

3. Create Better Methods

  • Try to make atomic function (Do not repeat yourself)
  • Break it into small section.
  • 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 finish.
  • Avoid output arguments
  • Do not offer anything extra. Methods must only do what the name suggests and nothing else.

4. Do not store the return values you dont need.

While writing the code we assume that we might need the return value of that method for later use and store them. When we dont use them we might be confusing the other programmers who are reading your code.

boolean available= User.isAvailable("23ftg");

You could have written it like this, however:

User.isAvailable("23ftg");

5. 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, and that means you could always find any deleted code again. You can goto this link to learn about GitHub.

6. Increase cohesion and decrease coupling.

Increased cohesion and decreased coupling do 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 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.

That is 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.

Whilst not realistic in some senarios it should be a design goal to work by.

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 }
}

Some where 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

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

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s