Skip to content

[Rule] Code Convention

Bear edited this page Apr 26, 2023 · 2 revisions

개요

몇가지를 제외한 대부분 경우는 sun의 java code convention를 따른다. https://www.oracle.com/technetwork/java/codeconvtoc-136057.html (무려 1999년도에 만들어진 문서)

코드 컨벤션

아래 설명에 대한 intellij 설정은 문서 참조

Code Formatting

인코딩

  • UTF-8 인텐드
  • 4 Space
  • Tab 사용 금지 (tab은 몇몇 소스 비교 툴에서 8 스페이스로 번역되어 코드가 엉망이 된다.) 한 줄 최대 길이
  • 120 줄이 넘어갈 때 operator는 새로운 줄의 앞에 둔다.
String xxx = "1234567890123456789012345678901234567890 " 
    + a1 + " "    + "1234567890"

코드 섹션을 주석 수평선을 그어 구분하고 싶을 때, 대 단위 구분은 '=' 문자를 쓰고, 그 외에는 중/소 단위는 '-'를 쓴다. 수평선은 120까지 늘인다.

// =========================================================
// 작업 1 시작
// =========================================================
     
    do something
     
    // -----------------------------------------------------
    // 작업 1-1 시작
    // -----------------------------------------------------
 
    do something
 
    // -----------------------------------------------------
    // 작업 1-2 시작
    // -----------------------------------------------------
 
    do something

중괄호 ( 브레이스 )

  • 브레이스의 시작 라인은 새로운 라인에 작성하지 않는다.
public void xxx()
{
    if () {
        처리
    } else {
        처리
    }
 
    while (1) {
        처리
    }
 
    for (;;) {
         처리
    }
.
    try () {
    }
    catch (Exception e) {
    }
}
  • if else 문의 내용이 한줄일 경우는 브레이스 생략 가능하다. (이 경우는 개발자가 하고 싶은대로 하면 된다.)
  • 중요! 반복문은 내용이 한줄이여도 강제 브레이스 사용. (한줄일 경우 브레이스 미 사용으로 발생할 수 있는 난독을 최소화 하자는 취지 → 반복문은 실수 하면 피해가 커짐) 네이밍
  • camel case를 사용한다.
  • boolean 타입 변수 혹은 boolean을 리턴하는 메소드는 "is" 혹은 "need" prefix로 시작한다. 띄어 쓰기
  • 연산자는 앞뒤로 무조건 한칸씩 띄운다.
  • 캐스팅 연산자의 괄호 뒤는 띄운다. 예: (int) num
  • 제어문 뒤와 시작 괄호 사이에 한칸 띄운다. (단 메소드는 메소드 이름과 괄호를 붙여 쓴다.)
  • 메소드 파라미터에 콤마 뒤에 한 칸 띄운다.
  • not 연산자는 붙여서 사용 (가독성을 위해 intellij에서 operator color 변경 추천 : Editor -> ColorSchema -> Language Defaults -> Operation Sign 색상 변경)
  • 한줄 주석 // 뒤에 한칸 띠운다. 연산자 비교 연산
  • 비교 연산이 복잡해 지는 경우, 논리 연산자 끼리는 무조건 괄호를 사용하여 단위별로 묶는다. (연산자 우선순위가 있지만, 실수를 줄이고 가독성 향상을 위하여 무조건 묶는다.)
if (((x > 1) || (x < 10)) && ((y > 1) || (y < 10)))
  • 할당 연산자에 비교 연산의 결과를 넣는 경우 괄호로 묶는다.
boolean isBoy = (age < 18);

static import 사용금지 (junit 제외)

  • 클래스 이름은 나름 일종의 설명인데, 이 설명을 생략한다는 것은 작명 컨벤션을 부정하는 것이다.
  • static import가 가독성을 높여 준다는 주장들은 동의할수가 없다. 가독성과 유지보수를 어렵게 만드는 주범중의 하나다.
  • oracle도 매우 조심하라고 경고하고 있다.
  • 우리 팀에서는 junit 한해서 사용가능.
  • junit에 static import는 거의 통념이 되가고 있기 때문이다. Optional 사용 제한
  • Optional은 메소드의 리턴으로만 사용 가능하다.
    • 메소드 체이닝에 속한 메소드가 null을 반환하여 NPE가 발생하는 경우를 방어할 때만 사용하자. (예: 자바8 stream은 메소드 체이닝 패턴으로 설계 되었기 때문에 Optional을 반환하는 메소드가 많이 있다.)
    • int, long, double 같은 주요 타입에서 사용할때는 OptionalInt, OptionalLong, OptionalDouble 사용할 것 (Optional에 Integer 넣으면 두번 boxing 하는 개념이기 때문에 더 무겁게 된다.)
  • Optional 사용 금지하는 경우
    • 맴버 변수로 사용 금지 (Optional은 serialize가 안됨)
    • 단순 null 체크를 위한 사용 금지 (Optional은 무겁다.)
    • Optional에 빈 컬렉션 넣지 말 것
    • 컬렉션 원소에 Optional 사용 금지 (map, list, set 모두 포함)
    • 메소드(생성자 포함) 인자로 Optional 사용 금지
  • 참고
    • 이펙티브 자바(3th) - item 55
  • TODO FIXME 두 종류만 사용.
// TODO:
앞으로 해야할 일
반드시 자세한 설명을 넣어야 함

// FIXME:
고쳐져야하는 코드
반드시 자세한 설명을 넣어야 함

메서드 시그니처 및 호출 부 포매팅 관련

  • 메서드 시그니처 혹은 호출 부의 파라미터가 길어질 경우 아래와 같이 처리를 한다.
---- 메서드 시그니처 ----
// DO NOT USE THIS
public void foo1(0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057)
{
   ... logic ...
}
 
// USE THIS
public void foo1(
    0x0051,
    0x0052,
    0x0053,
    0x0054,
    0x0055,
    0x0056,
    0x0057)
{
   ... logic ...
}
 
---- 호출 부 -----
// DO NOT USE THIS
foo1(0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057);
 
// USE THIS
foo1(
    0x0051,
    0x0052,
    0x0053,
    0x0054,
    0x0055,
    0x0056,
    0x0057);

메서드 체이닝 사용 시에 한 줄에 점(.) 1개만 허용한다

// DO NOT USE THIS
a().b().c();

// USE THIS
a().b()
    .c();

Spring Framework

생성자 주입

  • 생성자 위에 무조건 명시적으로 @Autowired를 달아준다.
    • 이유: 스샷에 설명한 것 처럼 실수로 @Service가 제외된 경우 바로 알수 있게된다.
  • 롬복의 @RequiredArgsConstructor를 사용할 시에는 무조껀 변수에 private final을 선언하여 처리한다.
    • 또한, 해당 어노테이션을 POJO, DTO 클래스에서 사용을 절대 금지한다.
  • 생성자의 파라미터는 한줄에 한개만 들어갈 수 있게 개행한다.
    • 이유: 스샷의 빨간 표시처럼 IDE의 도움을 받기 쉽게된다. image

Lombok 제약 사항

  • 링크

클래스, 메서드, 속성명 네이밍 룰

DB는 약어 유지 새로작성하는 코드의 클래스/메소드/필드명은 관용어(ctlg, cate) 또는 "특별한 약어 필요경우"를 제외하고는 풀네임 사용이 원칙

  • WSIN 사용 관용어 (추가가 필요한 경우 아래에 적어주세요)
    • 카탈로그 - ctlg
    • 카테고리 - cate 기존 코드는 작업 있을 경우 리팩토링 작명 예
  • 상품 이라는 단어를 클래스로 만들경우, DB의 용어 사전이 prod 라도 클래스명은 Prod가 아닌 Product를 사용
  • 상품명이라는 단어를 속성으로 만들경우, DB의 용어 사전이 prod_nm이라도 prodName으로 사용 DB 를 제외한 네이밍은 약어 지양, 직관적으로 풀어 정리 Date/Time 관련
  • LocalTime: ~Time (단어+Time의 조합이 명사)
  • LocalDate: ~Date (단어+Date의 조합이 명사)
  • LocalDateTime: (과거분사)~At
  • 기간에 대한 변수는 ~From, ~To를 붙인다. ex) collectorFromDate, collectorToDate Serializable 인터페이스를 최대한 구현하지 않는다.
  • 이펙티브 자바 규칙74 : Serializable 인터페이스를 구현할 때는 신중하라
  • 예외) 라이브러리 자체의 직렬화 / 역직렬화 방식이 존재하지 않을 경우에만 예외적으로 허용
    • e.g) Ehcache

시스템에서 사용하는 시간은 DB시간을 기준

서버 시간으로 사용할 경우 서버가 여러대일 때, 서버간 시간을 동기화 해주는 이슈가 발생할 수 있음

테스트

단위테스트 : 변경사항 발생시, 다른 곳에도 영향이 있는 상태일 경우 단위테스트를 적용

  • 모든 컴포넌트에 단위테스트를 진행하는 것이 아닌, 변경사항발생시 영향이 있는 경우에 사용
  • 단위테스트시 DB Insert하는 테스트를 Before로 지정하고, 테스트를 생성하면 수정, 삭제, 검색등의 테스트를 처리 가능 통합테스트 : Controller 기준 모든 메서드에 대해서 적용
  • 통합테스트는 리팩토링, 구조 변경시 기능이 정상적으로 동작하는 상태임을 보장하는 것이 목적임 테스트의 실행 시간은 400ms(vpn을 사용할경우 1초)를 넘지 않도록 한다 테스트 내부에 log()를 사용하고, print()는 사용하지 않는다.

Constant

지역 상수

  • 하나의 메서드 내부에서만 사용
  • 변할 가능성이 없는 상수 Class 상수
  • 두 개 이상의 메서드에서 사용
  • 변할 가능성이 있는 상수 (ex. path, size 등) interface static final
  • 전반에 걸쳐 공통으로 사용되는 상수

Parameter

메서드의 파라미터 개수가 4개 이상이면 Model (DTO, VO)를 만들어 전달한다.

반복적인 접근제어자 선언 FieldDefaults 사용

https://projectlombok.org/features/experimental/FieldDefaults final 의 경우 makefinal = true 와 @Nonfinal 을 활용 한다.

@Getter
@Setter
public class Pipe implements Serializable
{
    private String pipeId;
    private int mallId;
    private String status;
    private String productDisplay;
    private String allUrl;
 
    private String summaryUrl;
    private int isCrawling;
    private String dataProviderType;
    private String charset;
    private int productType;
 
    private String goodsType;
    private String currencyType;
    private int scheduleAllHour;
    private int scheduleAllMin;
    private int scheduleSummaryHour;
 
    private int scheduleSummaryMin;
}
@Getter
@Setter
@FieldDefaults(level= AccessLevel.PRIVATE)
public class Pipe implements Serializable
{
    String pipeId;
    int mallId;
    String status;
    String productDisplay;
    String allUrl;
 
    String summaryUrl;
    int isCrawling;
    String dataProviderType;
    String charset;
    int productType;
 
    String goodsType;
    String currencyType;
    int scheduleAllHour;
    int scheduleAllMin;
    int scheduleSummaryHour;
 
    int scheduleSummaryMin;
}
@Getter
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Test
{
    @NotNull
    String reviewId;
 
    @NotNull
    String pipeId;
 
    @NotNull
    String productId;
 
    String buyOption;
 
    @NotNull
    Integer mallId;
 
    @NotNull
    String mallReviewId;
 
    @NotNull
    String mallName;
 
    @NotNull
    String storePid;
 
    @NotNull
    String storeUserId;
 
    String title;
 
    @NotNull
    String content;
 
    @NotNull
    String reviewLink;
 
    String displayYn;
 
    @NotNull
    Integer productSatisfyLevel;
 
    @NotNull
    @JsonProperty("registered_date")
    Long registeredAt;
 
    @NotNull
    @JsonProperty("updated_date")
    Long updatedAt;
 
    @NotNull
    String documentHash;
 
    @NonFinal
    @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
    List<Image> images;
}

Annotation은 짧은 순서대로 정리

@FieldDefaults(level = AccessLevel.PRIVATE)
@Getter
@NoArgsConstructor
public class ReviewEP

@Getter
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ReviewEP

모든 약어는 대문자 표기

Ep → EP ReveiwEp → ReviewEP