열심히 기능을 짜다보면, 인자가 2~3개 넘어가는 일이 다반사입니다.
로버트 C. 마틴의 클린코드에서 말하듯이, 3개 이상의 인자는 죄악입니다. (이렇게 까지 강하게 표현했는지 정확하게 기억은 나지 않지만.. 어쨌든 확실히 나쁩니다)
3개 보다 나은 것은 2개, 2개보다 나은 것은 1개의 인자, 가장 좋은 것은 0개의 인자를 활용하는 것입니다.
0개의 인자가 어떻게 가능한지 처음 들으면 의아할 수 있는데, 객체를 생성하고 객채의 메소드로 기능을 구성하라는 말입니다.
가장 좋은건 0개가 맞지만, 열심히 비지니스 로직을 짜다보면 2개 정도의 인자까지는 봐줄만 한 것 같습니다. 하지만, 이 때도 최대한 메서드명을 잘 지어서 실수를 방지해야 합니다.
예를 들어, 게시글의 그룹명과 카테고리명이 따로 존재하고, 이 2개를 토대로 게시글을 찾아와야 하는 메소드가 존재한다고 생각해 봅시다. 다음은 Java 코드입니다.
public getPostBy(String group, String category) {
...
}
뭐가 문제일까요? 우선, group 과 category 가 같은 String type 이라는 것입니다. 이 메서드를 호출하는 입장에서, 첫번째가 group 이고 두번째가 category 인지 어떻게 알 수 있을까요? 함수로 들어가 구현을 확인하기 전까진 알 수 없습니다. 이는 다른 순서로 인자를 전달하게 되는 실수를 유발합니다.
차라리 type 이 다르면 괜찮을 수 있습니다.
public getPostBy(Group group, Category category) {
...
}
적어도 다른 순서로 인자를 전달했을 때 에러가 발생하기 때문에 런타임 오류가 발생할 일은 없을 것입니다. 다만.. 여전히 함수에 들어가서 확인해 봐야 한다는 단점이 있습니다.
따라서, 저는 인자가 2개일 때에는 다음과 같이 메서드 이름을 짓습니다.
public getPostByGroupAndCategory(String group, String category) {
...
}
인자를 전달해야 하는 순서가 메서드명에 드러납니다. 이를 통해 실수가 발생할 확률이 현저히 낮아질 수 있습니다.
하지만, 역시나 가장 좋은 방법은 DTO 를 생성하는 것입니다.
import lombok.Builder;
@Builder
public class GetPostDTO {
String group;
String category;
}
public getPost(GetPostDTO dto) {
...
}
public caller() {
GetPostDTO dto = GetPostDTO.builder()
.group("GROUP")
.category("CAETGORY")
.build();
getPost(dto);
}
이렇게 DTO 를 생성해주면, 순서가 존재하지 않기 때문에, 인자를 잘못 전달하는 실수가 발생할 확률도 현저히 줄어들며 3~4 개 등 인자가 추가되어도 문제가 없어집니다.
다음으로, 이 포스팅을 작성하게 된 계기인 실무에서의 저의 실수를 소개해 보겠습니다.
나름 클린코드를 읽고 깨끗하게 코드를 짜는 것에 대한 의무를 느끼고 있었기에, 인자가 3개를 넘어가자 DTO 를 생성하기로 마음 먹었습니다. 그러고 작성한 코드가 다음과 같습니다.
private ag.act.model.StockHomeSectionResponse generateListSectionWithListItems(String stockCode, BoardCategory boardCategory) {
final Board board = boardService.getBoardByStockCodeAndCategory(stockCode, boardCategory);
final List<ag.act.model.SectionItemResponse> listItems = generateListItems(stockCode, board.getCategory());
if (listItems.isEmpty()) {
return null;
}
GenerateBoardGroupCategoryLinkDto dto = getGenerateBoardGroupCategoryLinkDto(
stockCode, boardCategory.getName().toLowerCase(), board.getGroup().name().toLowerCase()
);
return stockHomeSectionResponseConverter.convert(dto, board.getCategory().getName(), listItems);
}
private GenerateBoardGroupCategoryLinkDto getGenerateBoardGroupCategoryLinkDto(
String stockCode, String boardGroupName, String boardCategoryName
) {
return GenerateBoardGroupCategoryLinkDto.builder()
.stockCode(stockCode)
.boardGroupName(boardGroupName)
.boardCategoryName(boardCategoryName)
.build();
}
실수가 보이시나요? 기껏 DTO 를 생성해 놓고, 그걸 생성하기 위한 메서드를 만들었습니다. 그리고 그 메서드를 호출할 때 boardGroupName 을 넣어야하는 순서에 boardCategory 를 넣었습니다.
결국, 이는 엉뚱한 Link 를 생성했고, 클라이언트 개발자 분이 발견한 후 문제를 제기해주셨습니다. 부끄러운 일이죠.. 위 코드는 다음과 같이 고쳐져야 합니다.
private ag.act.model.StockHomeSectionResponse generateListSectionWithListItems(String stockCode, BoardCategory boardCategory) {
final Board board = boardService.getBoardByStockCodeAndCategory(stockCode, boardCategory);
final List<ag.act.model.SectionItemResponse> listItems = generateListItems(stockCode, board.getCategory());
if (listItems.isEmpty()) {
return null;
}
GenerateBoardGroupCategoryLinkDto dto = GenerateBoardGroupCategoryLinkDto.builder()
.stockCode(stockCode)
.boardGroupName(board.getGroup().name().toLowerCase())
.boardCategoryName(boardCategory.name().toLowerCase())
.build();
return stockHomeSectionResponseConverter.convert(dto, board.getCategory().getName(), listItems);
}
굳이 dto 를 생성하는 메서드를 추가할 필요가 없습니다.
한 가지가 더 수정된 것을 눈치채신 분도 있을 것 같은데, 사실 이 코드에는 한가지 오류가 더 있습니다. 바로 boardCategory.getName() 과 boardCategory.name() 의 용도가 다르다는 것입니다. name() 은 말그대로 Enum 의 key 를 가져오고요, getName() 은 화면에 표시되어야 할 한글 문구를 가져옵니다. 여기서 링크를 생성할 때는 key 를 가져와야 하는데, 한글 문구로 링크를 생성했던 것입니다.
과연 누가 name 과 getName 이 다르게 동작한다는 것, 어떤 것을 사용해야 되는지를 구분할 수 있을까요? 명백한 저의 실수입니다.. name() 은 java 에서 기본적으로 제공해 주는 것이기 때문에 건드릴 수도, 건드릴 필요도 없구요, 제가 생성했던 getName 을 getDisplayName 정도로만 변경해 주어도 실수를 줄일 수 있을 것입니다.
문제는 해결했습니다. 실수를 방지하는 방법에 대하여 클린 코드에 관해서만 설명했지만, 사실 이 실수를 예방하기 위한 가장 중요한 한가지가 빠졌습니다.
그건 바로, 테스트 코드 작성 입니다. 테스트 코드만 잘 작성했어도 실수가 포함된 채로 개발 서버에 배포되어, 클라이언트 작업자에게 까지 전달되지는 않았을 것입니다. 바쁘다는 이유로 간단하게 직접 API 만 호출해보고, 테스트 코드 작성을 뒤로 미뤘었는데, 결국 커뮤니케이션과 수정작업에 비용이 더 들게 되는 상황이 되었습니다. 반성합니다..
결론은, 코드는 최대한 클린하게, 테스트 코드는 필수로 작성하자! 입니다.