코드의 첫 90%에 개발 시간의 첫 90%가 쓰인다. 코드의 나머지 10%에 개발 시간의 마지막 90%가 쓰인다. - 톰 카길
9-1 정적 코드 분석 도구
- SpotBugs( https://spotbugs.github.io/) 자바에서 가장 오래된 정적 코드 분석도구이다.
- Checkstyle(https://checkstyle.sourceforge.io/), PMD(https://pmd.github.io/) 도 유명하나, 다소 장황하고 특정 코드 방식을 고집하는 등 선호도를 탄다.
- Error Prone(https://errorprone.info/) 은 ConcurrentHashMap 내 버그를 감지하면서 유명해 졌는데, 구글이 자체 개발했다.
- Code Inspection( https://www.jetbrains.com/help/idea/code-inspection.html ) 은 IntelliJ 에서 사용할 수 있게 해준다.
9-2. 팀 내 자바 포맷 통일
팀 내에서 자바 코드의 포맷을 맞추는게 좋다.(하다못해 탭의 표준이나, 한 행에 최대 문자 수 등)
팀 내에 논쟁의 소지가 있다면 업계 표준을 쓰는 것도 좋다.
( 구글 스타일 가이드 : https://google.github.io/styleguide/javaguide.html)
서식화를 검증하는 자바 포맷도 있다. (https://github.com/google/google-java-format)
9-3. 빌드 자동화
어떤 개발 툴(IntelliJ, eclipse, Net Beans)이나 OS를 사용하더라도 빌드를 독립적으로 수행할 수 있도록 빌드도구를 사용하라. Gradle, maven, Ant 등이 있다.
9-4. 지속적 통합
빌드를 자동화하였다면, 테스트를 수행하고 배포를 할 수 있는 통합도구가 필요하다.
Jenkins를 사용하거나 전용 품질분석 서버(SonarQube, Travis-CI, Codacy) 를 검토하라.
9-5. 생산준비와 납품
사용자가 사용하게 되면 로그를 한 곳에 모으고, 로그를 검색/분석, 대시보드로 시각화하여야 한다. ELK와 Graylog를 검토하라.
9-6. 콘솔 출력 대신 로깅
System.out.println(), System.err.println()은 에러를 분석하는데도 한계가 있으며, 매번 String을 병합하는 비용도 든다.
log4j를 활용해서 debug, info, warn, error, fatal등 로그 수준에 맞게 로그를 남겨야 한다.
9-7. 다중 스레드 코드 최소화 및 독립
병렬 실행은 여러 동시성 이슈를 야기하며, 테스트를 제대로 하기도 어렵다. 성능 이슈가 없다면 다중 스레드 코딩은 사용하지 않고, 성능이슈가 발생할 때만 고려하자. 동시 실행을 구현한다면 최소한의 패키지에서만 사용하도록 독립적으로 구현하라.
9-8. 고급 동시 실행 추상화 사용하기
동시 실행에 대해 주의하고, AtomicInteger, LongAdder, ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue와 같은 자료구조를 살펴보라.
9-9. 프로그램 속도 향상
List<Supply> supplies;
long countDifferentKinds() {
return supplies.stream()
.sequential() // this can be omitted
.filter(Supply::isUncontaminated)
.map(Supply::getName)
.distinct()
.count();
}
위와 같이 sequential 을 사용하면 한번에 하나씩 연산하는 것을 스트림의 parallel을 이용해면, CPU의 여러 코어가 동시 작업이 가능하다. 단, Sort()나 forEachOrdered() 를 써야 한다면 sequential이 더 빠를 수 있다.
List<Supply> supplies;
long countDifferentKinds() {
return supplies.stream()
.parallel()
.filter(Supply::isUncontaminated)
.map(Supply::getName)
.distinct()
.count();
}
9-10. 틀린 가정 알기
class NameTag {
final String name;
NameTag(String fullName) {
this.name = parse(fullName).toUpperCase();
}
String parse(String fullName) {
String[] components = fullName.split("[,| ]");
if (components == null || components.length < 2) {
return fullName;
}
if (fullName.contains(",")) {
return components[0];
} else {
return components[components.length - 1];
}
}
}
class Usage {
static void main(String[] args) {
System.out.println(new NameTag("Neil Armstrong").name);
System.out.println(new NameTag("Neil Alden Armstrong").name);
System.out.println(new NameTag("Armstrong, Neil").name);
System.out.println(new NameTag("Edwin Eugene Aldrin, Jr.").name);
System.out.println(new NameTag("William \"Bill\" H. Gates III").name);
System.out.println(new NameTag("刘伯明").name);
}
}
이름의 성과 같은 부분은 나라마다 틀리고, 사람마다 틀리는 예외가 항상 발생할 수 있다. 호출하는 사용자가 직접 설정하게 하는게 나을 수 있다.
class NameTag {
final String name;
NameTag(String name) {
Objects.requireNonNull(name);
this.name = name;
}
}
class Usage {
static void main(String[] args) {
System.out.println(new NameTag("ARMSTRONG").name);
System.out.println(new NameTag("ARMSTRONG").name);
System.out.println(new NameTag("ALDRIN").name);
System.out.println(new NameTag("ARMSTRONG").name);
System.out.println(new NameTag("GATES").name);
System.out.println(new NameTag("刘").name);
}
}