JavaArgs

Software Engineering Course Assignment
Submitted by Raghav Mittal [2018701023]
IIIT-H



JavaArgs is a java version of Args that is described in the book "Clean Code: A Handbook of Agile Software Craftsmanship" by Robert C. Martin a.k.a. Uncle Bob:older_man:.

This piece of code has been originally forked from the Args program described in: Uncle Bob :octocat:. I have further tried to implement a clean coding practice on this source code written in Java☕ as part of Assignment 📝.

Table of Contents

  1. Usage 🏃✔️
    1.1Schema
    1.2 ArgsMain.java
  2. Understanding the code
    2.1 JavaDocs 📃
  3. Objective - Clean code 🎯
    3.1 Code coverage
    3.3 Linting improvements 🚩
    3.2 Code smells ♨️
  4. Licence 📄

You will need to implement a main class to run this file.

Schema

Refer to the following schema to implemnt main class:

Schema types Argument data types
char boolean
char* string
char# Integer
char## double
char[*] one element of a string array.

Example schema: (f,s*,n#,a##,p[*])

Coresponding command line:

"-f -s Bob -n 1 -a 3.2 -p e1 -p e2 -p e3"

ArgsMain.java

You may use the following code to implement the main class ArgsMain.java

package com.cleancoder.args;

import java.util.Map;

public class ArgsMain {

  /**
  * This method is used to add two integers. This is
  * a the simplest form of a class method, just to
  * show the usage of various javadoc Tags.
  * @param args This is the first paramter to addNum method
  */
  public static void main(String[] args) {
    try {
      Args arg = new Args("l,p#,d*,b##, c[*], m&", args);
      boolean logging = arg.getBoolean('l');
      int port = arg.getInt('p');
      double dob = arg.getDouble('b');
      String[] cs = arg.getStringArray('c');
      Map<String, String> ma = arg.getMap('m');
     
      String directory = arg.getString('d');
      executeApplication(logging, port, directory, dob, cs, ma);
    } catch (ArgsException e) {
      System.out.printf("Argument error: %s\n", e.errorMessage());
    }
  }

  private static void executeApplication(boolean logging, int port, String directory, double dob, String[] cs, Map<String, String> ma) {
    System.out.printf("logging is %s, port:%d, directory:%s, double:%f, stringArray:%s, map:%s",logging, port, directory, dob, cs[0], ma);
  }
}

Understanding the Code

JavaDocs 📃

To understand the code better follow the JavaDocs given for the code.

Javadocs for tests are not added. For more discussion on JavaDocs for test check this Stackoverflow.

Objective - Clean Code 🎯

The goal of this exercise is to implement clean code practices in the code for JavaArgs and that include the following :

  1. Improve code-coverage
  2. Improve linting
  3. Remove code smells

For the above changes some tools were used which needs to be mentioned here :

Code Coverage:

  • Code coverage has been imporved from 88.5% to 91% overall.
  • Code coverage is 97% if tests are not considered. As per the discussion online, tests are generally not covered during the coveraged for more information check this Stackoverflow link.

Code Coverage Improvements

Below mentioned are the refactored pieces of code that helped imporve Code Coverage:

ArgsException.java

Unused lines were removed❌ as they were affecting it significantly.

// these lines were removed
public  ArgsException() {}

public  ArgsException(String message) {super(message);}

public void setErrorCode(ErrorCode  errorCode) {  this.errorCode = errorCode;}

ArgsExceptionTest.java

testOkMessage() was added➕ because OK enum was not covered

// following lines were added
public void testOkMessage() throws Exception {

ArgsException e = new ArgsException(OK, 'x', null);

assertEquals("TILT: Should not get here.", e.errorMessage());

}

Linting Improvements 🚩

ArgsData.java

Following unused line was removed

public ArgsData() {}

ArgsException.java

Return statement shifted from outside of switch case to default case inside switch case.

Modifications🔶 done accross the package

  • Removed wildcards from all import statement in the project. [also from java utils]

  • There are various ways to add imports in java and one of the two ways is adding imports with wildcards (*), I chose to remove all the wildcards and manually add imports taking a reference from the following discussion Stackoverflow.

  • Followed some code conventions where I added static imports before all the util imports and arranged alphabetically.

Code Smells ♨️

Major code smells were found in the code. In order to remove those code smells, modifications🔶 were done in Args.java and a new file ArgsData.java was added➕.

The changes that are stated below are in-coherence with the following manual which clearly states the decomposition methodology. For details refer to Jdeodrant.

Smell type Refactoring Type Variable Criteria File modified🔶/added
Long method Extract method m Args.java modified
God Class Extract method schema ArgsData.java added;
Args.java modified

File ArgsData.java was created➕ in order to decompose the god class. Following Code from Args.java has been decompsed into ArgsData.java

private void parseSchemaElement(String element) throws ArgsException { 
    char elementId = element.charAt(0); 
    String elementTail = element.substring(1); 
    validateSchemaElementId(elementId); 
    if (elementTail.length() == 0) 
      marshalers.put(elementId, new BooleanArgumentMarshaler()); 
    else if (elementTail.equals("*")) 
      marshalers.put(elementId, new StringArgumentMarshaler()); 
    else if (elementTail.equals("#")) 
      marshalers.put(elementId, new IntegerArgumentMarshaler()); 
    else if (elementTail.equals("##")) 
      marshalers.put(elementId, new DoubleArgumentMarshaler()); 
    else if (elementTail.equals("[*]")) 
      marshalers.put(elementId, new StringArrayArgumentMarshaler()); 
    else if (elementTail.equals("&")) 
      marshalers.put(elementId, new MapArgumentMarshaler()); 
    else 
      throw new ArgsException(INVALID_ARGUMENT_FORMAT, elementId, elementTail); 
  } 

  private void validateSchemaElementId(char elementId) throws ArgsException { 
    if (!Character.isLetter(elementId)) 
      throw new ArgsException(INVALID_ARGUMENT_NAME, elementId, null); 
  } 

public boolean getBoolean(char arg) { 
    return BooleanArgumentMarshaler.getValue(marshalers.get(arg)); 
  } 

  public String getString(char arg) { 
    return StringArgumentMarshaler.getValue(marshalers.get(arg)); 
  } 

  public int getInt(char arg) { 
    return IntegerArgumentMarshaler.getValue(marshalers.get(arg)); 
  } 

  public double getDouble(char arg) { 
    return DoubleArgumentMarshaler.getValue(marshalers.get(arg)); 
  } 

  public String[] getStringArray(char arg) { 
    return StringArrayArgumentMarshaler.getValue(marshalers.get(arg)); 
  } 

  public Map<String, String> getMap(char arg) { 
    return MapArgumentMarshaler.getValue(marshalers.get(arg)); 
  } 
} 

License 📄

MIT