본문 바로가기

PMD

[한글화 시리즈-18] String and StringBuffer Rules

String and StringBuffer Rules

이 룰셋은 String 및 StringBuffer 사용 상 발생되는 다양한 문제들에 관한 룰들을 포함한다.

(단 StringBuffer는 사용 구조상 StringBuilder와 매우 비슷하다. 하지만,  threadsafe 기능 때문에 StringBuilder보다는 느리지다. PMD에는 StringBuffer만 기술되어 있지만, StringBuffer와 StringBuilder는 매우 비슷하므로, 스레드 동기화 등의 기능이 필요하지 않다면, StringBuilder 사용을 고려하자.)



AvoidDuplicateLiterals

중복되는 문자열은 하나의 상수 필드(constant field)로 정의하는 것이 효율적이다.PMD는 기본적으로 4개 이상의 중복된 문자열을 경고한다. annotation에 사용된 문자열은 skipAnnotation 옵션을 사용하여 무시할 수 있다. 

//중복된 문자열 사용은 비효율적이다.
public class Foo {
  private void bar() {
    buz("Howdy");
    buz("Howdy");
    buz("Howdy");
    buz("Howdy");
  }
  private void buz(String x) {}
}

//중복된 문자열은 하나로 정의하자.
public class Foo {
  private final static String MSG = "Howdy";
  private void bar() {
    buz(MSG);
    buz(MSG);
    buz(MSG);
    buz(MSG);
  }
  private void buz(String x) {}
}

StringInstantiation

String 객체를 생성할 경우 new String을 이용한 인스턴스화는 불필요하다.

public class Foo {
  private String bar = new String("bar"); 
  // 그냥 다음과 같이 하자
  String bar = "bar";
}

StringToString

String 객체에서 toString 메소드를 사용하는 것은 무의미하다.

public class Foo {
  private String baz() {
    String bar = "howdy";
    //.toString()을 왜 하지?
    return bar.toString();
  }
}

InefficientStringBuffering

StringBuffer의 생성자나 append() 메소드안에서 문자열이 아닌 객체를 결합하는 등의 일은 피하자.

public class Foo {
  void bar() {
    // 다음과 같은 일은 피하자
    StringBuffer sb=new StringBuffer("tmp = "+System.getProperty("java.io.tmpdir"));
    // 다음과 같이 나눠서 작업하자.
    StringBuffer sb = new StringBuffer("tmp = ");
    sb.append(System.getProperty("java.io.tmpdir"));
  }
}

UnnecessaryCaseChange

equalsIgnoreCase()는 toUpperCase/toLowerCase().equals()보다 빠르다. 불필요하게 대문자로 소문자로 바꾼 후 문자열을 비교하는 일은 피하자.

public class Foo {
  public boolean bar(String buz) {
    // buz.equalsIgnoreCase("baz")이 더 빠르다.
    return buz.toUpperCase().equals("baz");
    // toUpperCase()를 equalsIgnoreCase()이전에 실행할 이유가 없다.
    // return buz.toUpperCase().equalsIgnoreCase("baz");
  }
}


UseStringBufferLength

문자열의 길이를 확인하기 위해서 StringBuffer.toString().equals("")로 빈 문자열을 확인 하는 작업이나, StringBuffer.toString().length() == 로 길이를 확인할 필요 없이, StringBuffer.length()로 대신하자.

public class Foo {
  void bar() {
    StringBuffer sb = new StringBuffer();
    // 불필요한 String 변환 작업을 실행한다.
    if(sb.toString().equals("")) {}
    // 길이는 간단히 length() 메소드를 사용하자.
    if(sb.length() == 0) {}
  }
}

AppendCharacterWithChar

character와 같은 경우 불필요하게 String 형태로 StringBuffer에 넣을 필요가 없다. (character는 double quotation 말고 single quotation을 쓰자!)

public class Foo {
  void bar() {
    StringBuffer sb=new StringBuffer();
    // 큰따옴표("")는 String을 의미하며 character와 같은 경우
    // 다음과 같이 작은 따옴표('')를 이용하여 character형태로
    // 추가시키는 것이 더욱 효율적이다.
    sb.append("a");

    // character는 character 형 그대로 추가하자.
    StringBuffer sb=new StringBuffer();
    sb.append('a');
  }
}

ConsecutiveLiteralAppends

연속되는 문자열을 연속적으로  StringBuffer.append 메소드를 호출하는 것을 피하자. PMD는 1개 이상의 연속되는 문자열 추가를 경고한다.

public class Foo { private void bar() { StringBuffer buf = new StringBuffer(); //"Hello World"와 같이 연속적인 문자열을 //조각내서 추가하지 말자. buf.append("Hello").append(" ").append("World"); //아래와 같이 연속되는 문자열은 한번에 추가하자. buf.append("Hello World"); } }


UseIndexOfChar

하나의 character의 위치를 확인할 경우에는 String.indexOf(char)를 사용하자. 이 메소드가 더욱 빠르게 실행된다.

public class Foo {
  void bar() {
    String s = "hello world";
    // character를 String으로 사용하지말자!
    if (s.indexOf("d") {}
    // character를 정상적으로 사용한 방법
    if (s.indexOf('d') {}
  }
}

InefficientEmptyStringCheck

String.trim().length()는 문자열이 공백 문자열인지 확인하는데 매우 비효율적이다. 이 방식은 new String 객체를 단지 문자열의 길이를 확인하기 위해서만 생성한다. static 메소드와 반복문을 이용해서 문자열 전체를 character 단위로 Caracter.isWhitespace()를 확인하고 공백 문자가 아닌 문자가 있다면 false를 반환한다.

public class Foo {
  void bar(String string) {
    //이와 같은 방식은 불필요한 String 객체를 메모리에 할당하여 비효율적이다.
    if (string != null && string.trim().size() > 0) {
      ...
    }
  }
}

//다음과 같은 메소드를 활용하자
public static boolean isBlank(final String str) {
  int strLen;
  boolean result = true;
  if (str == null || str.length() == 0) {
    result = true;
  } else {
    strLen = str.length();
    for (int i = 0; i < strLen; i++) {
      if (Character.isWhitespace(str.charAt(i))) {
        result = false;
        break;
      }
    }
  }
  return result;
}

InsufficientStringBufferDeclaration

부적절하게 미리 사이즈가 정의된 StringBuffer의 문제점은 프로그램 실행 중에 사이즈를 변경하는 작업이 빈번하게 발생한다는 것이다. 이 룰은 실제로 StringBuffer.appned() 메소드로 전달되는 character들을 체크하여 최악의 경우(worst case) 시나리오를 바탕으로 경고한다. 비어있는 StringBuffer의 생성자는 버퍼의 길이를 16으로 초기화한다. 

public class Foo {
  void bar() {
    //기본 생성자를 이용하여 버퍼의 길이는 16이지만, 실제 입력된 문자열은
    //16자리를 넘어서 버퍼가 지속적으로 사이즈를 변경하는 작업을 수행한다.
    StringBuffer bad = new StringBuffer();
    bad.append("This is a long string, will exceed the default 16 characters");

    //버퍼의 길이를 미리 문자열의 길이인 41 설정항 사이즈 변경이 불필요하다.
    StringBuffer good = new StringBuffer(41);
    good.append("This is a long string, which is pre-sized");
  }
}

UselessStringValueOf

문자열에 다른 자료형의 변수를 결합하기위해서 String.valueOf를 사용할 필요 없이 + 연산자로 바로 연결하는 것이 좋다.

public String convert(int i) {
  String s;
  s = "a" + String.valueOf(i); // valueOf를 사용할 필요가 없다.
  s = "a" + i;                 // 그냥 + 로 연결하자.
                               // 단 숫자형을 문자형으로 변경할 경우에만
                               // "" + i가 아닌 Integer.toString를 사용하자.
  return s;
}

StringBufferInstantiationWithChar

StringBuffer sb = new StringBuffer('c'); 이와 같은 생성자에 char를 넣어서 초기화는 방법은 피하자. 이와 같은 경우, 개발자의 의도와는 다르게, char는 자동을 int로 형변환되어 StringBuffer의 size를 결정하는 용도로 사용된다. 'c'를 초기 값으로 사용하고 싶을 경우 "c"와 같이 String형으로 전달해야한다.

class Foo {
  //char는 int로 자동으로 형변환되어 StringBuffer의 길이를 결정
  StringBuffer sb1 = new StringBuffer('c'); 
  //"c"로 사용해야한다.
  StringBuffer sb2 = new StringBuffer("c");
}

UseEqualsToCompareStrings

문자열(String) 비교를 위하여 == 또는 !=를 사용하지말자. 만약 new String("Hello").intern()과 같이 intern version으로 생성한 경우에는 사용할 수 있다. 하지만, intern()을 사용할 경우 String객체를 생성해야하며, String pool에 등록되어 사용되어지므로 GC의 대상이 될 수 없다.


AvoidStringBufferField

StringBuffer의 크기가 매우 커지고, 사용 시간이 길어지면 메모리 누수(memory leak)의 원인이 될 수 있다.

//Foo 클래스가 매우 긴 시간동안 사용되고,
//이 클래스 포함한 StringBuffer의 크기가 매우 커진다면,
//StringBuffer는 메모리 누수의 원인이 될 수 있다.
class Foo {
  private StringBuffer memoryLeak;
}

해당 URL: http://pmd.sourceforge.net/pmd-4.2.6/rules/strings.html